Following up on this question.
I've managed to extract pixel informations off a bitmap instance using Bitmap.LockBits. PixelFormat is Format48bppRbg which, based on my understanding and Peter's answer on the aforementioned question, should store each pixel's RGB color channels using two bytes for storage. The bytes's combined value should be equal to a number between 0 and 8192, representing an RBG channel intensity value. That value is obtained by passing both bytes to the BitConverter.ToUInt16 method.
So upon extracting pixel informations for the following 3 x 3 bitmap (magnified here for clarity):
I'm having first row of pixel channels going like this:
Pixel color
Red intensity
Green intensity
Blue intensity
RED
8192
0
0
GREEN
0
8192
0
BLUE
0
0
8192
So far so good.
On the second row, however, it goes like this:
Pixel color
Red intensity
Green intensity
Blue intensity
WHITE
8192
8192
8192
GRAY (!)
1768 (!)
1768 (!)
1768 (!)
BLACK
0
0
0
The white and black pixel channel values make sense to me.
The gray, however, doesn't.
If you use any color picker on the gray pixel above, you should get a perfectly medium gray. In 24 bits color it should be the equivalent of (R: 128, G: 128, B: 128), or #808080 in hexadecimal form.
Then how come, in a Format48bppRpg pixel format, the channels intensity is way below the expected, middle 4096 value? Isn't this GDI+ based range of 0-8192 supposed to work like its 8 bits counterpart, with 0 being the lowest intensity and 8192 the highest? What am I missing here?
For reference, here is a screen capture from Visual Studio debugger showing the raw bytes indexes and values, with additional notes on the stride, channels positions and their extracted intensity value, up to the gray pixel:
The part where you state an incorrect assumption is that #808080 is "perfectly medium gray". It is not, at least not if you look at it from a certain way (more about it here).
Many color standards, including sRGB, use gamma compression to make darker colors more spaced out in the range of 256 values normally used to store RGB colors. This roughly means taking a square root (or 2.2-root) of the relative component value before encoding this value. The reason is that the human eye (like other senses) perceives brightness logarithmically, thus it is important to represent even an arithmetically small change in the brightness if it would mean actually doubling it, for example.
The byte value of 128 is actually about 21,95 % (128/255 ^ 2.2) of full brightness, which is what you're seeing in the case of 16-bit components. The space of possible values there is much larger, thus GDI (or the format) doesn't need to store them in a special way anymore.
In case you need an algorithm, taking the 2.2-root of the value works mostly well, but the correct formula is a bit different, see here. The root function normally has an infinite slope near zero, so the specific formula attempts to fix that by making that portion linear. A piece of code can be derived from that quite easily:
static byte TransformComponent(ushort linear)
{
const double a = 0.055;
var c = linear / 8192.0;
c = c <= 0.0031308 ? 12.92 * c : (1 + a) * Math.Pow(c, 1/2.4) - a;
return (byte)Math.Round(c * Byte.MaxValue);
}
This gives 128 for the value of 1768.
Related
I am trying to write an application that can extract images from a cinefilm (e.g. Super8 or Normal8) in order to generate a video from the extracted images. I am trying to do that in WPF using .NET 5.0 and C#.
Some of the images need color corrections ( brighter, darker, sharper, etc );
.NET 5.0 provides a structure System.Windows.Media.Color which provides the methods Add(Color1,Color2) and Subtract(Color1, Color2).
If you perform this methods in a C# program it might happen, that you get color channel results that should have argb values below 0 or above 255.
I expect a value of 0 (zero) for negative results and a value of 255 for results above 255.
--- sample -----------------------------------------------------------------------------------------
//
// the correction color is defined as a static System.Windows.Media.Color
// e.g.like Color.FromArgb(255, 20, 15, 10);
//
public Color CorrectARGBValues(byte a, byte red, byte green, byte blue)
{
System.Windows.Media.Color aOldColor = Color.FromArgb(a, red, green, blue);
System.Windows.Media.Color aNewColor = Color.Add(aOldColor, aCorrectionColor);
return aNewColor;
}
The mentioned functions seem not to handle this exceptions - e. g. by inserting 0 for a negative value or inserting 255 for a value above 255.
Can anybody recommend a simple solution to this problem.
I cannot find anything appropriate to this simple problem in the MS Documentation nor in the Internet.
A solution may be to handle each color pixel in a WhriteableBitmap separately, but that seems to be extremely awkward.
Regards
Wolfgang
So I have, from an external native library (c++) a pixel buffer that appears to be in 16Bit RGB (SlimDX equivalent is B5G6R5_UNorm).
I want to display the image that is represented by this buffer using Direct2D. But Direct2D does not support B5G6R5_UNorm.
so I need to convert this pixel buffer to B8G8R8A8_UNorm
I have seen various code snippets of such a task using bit shifting methods, but none of which were specific for my needs or formats. It doesn't help i have zero, nada, none, zilch any clue about bit shifting, or how it is done.
What i am after is a C♯ code example of such a task or any built in method to do the conversion - I don't mind using other library's
Please Note : I know this can be done using the C♯ bitmap classes, but i am trying to not rely on these built in classes (There is something about GDI i don't like), the images (in the form of pixel buffers) will be coming in thick and fast, and i am choosing SlimDX for its ease of use and performance.
The reason why I believe I need to convert the pixel buffer is, if I draw the image using B8G8R8A8_UNorm the image has a green covering and the pixels are just all over the place, hence why i believe i need to first convert or 'upgrade' the pixel buffer to the required format.
Just to add : When i do the above without converting the buffer, the image doesn't fill the entire geometry.
The pixel buffers are provided via byte[] objects
Bit shifting and logical operators are really useful when dealing with image formats, so you owe it to yourself to read more about it. However, I can give you a quick run-down of what this pixel format represents, and how to convert from one to another. I should preface my answer with a warning that I really don't know C# and its support libraries all that well, so there may be an in-box solution for you.
First of all, your pixel buffer has the format B5G6R5_UNORM. So we've got 16 bits (5 red, 6 green, and 5 blue) assigned to each pixel. We can visualize the bit layout of this pixel format as "RRRRRGGGGGGBBBBB", where 'R' stands for bits that belong to the red channel, 'G' for bits that belong to the green channel, and 'B' for bits that belong to the blue channel.
Now, let's say the first 16 bits (two bytes) of your pixel buffer are 1111110100101111. Line that up with the bit layout of your pixel format...
RRRRRGGGGGGBBBBB
1111110100101111
This means the red channel has bits 11111, green has 101001, and blue has 01111. Converting from binary to decimal: red=31, green=41, and blue=15. You'll notice the red channel has all bits set 1, but its value (31) is actually smaller than the green channel (41). However, this doesn't mean the color is more green than red when displayed; the green channel has an extra bit, so it can represent more values than the red and blue channels, but in this particular example there is actually more red in the output color! That's where the UNORM part comes in...
UNORM stands for unsigned normalized integer; this means the color channel values are to be interpreted as evenly spaced floating-point numbers from 0.0 to 1.0. The values are normalized by the number of bits allocated. What does that mean, exactly? Let's say you had a format with only 3 bits to store a channel. This means the channel can have 2^3=8 different values, which are shown below with the respective decimal, binary, and normalized representations. The normalized value is just the decimal value divided by the largest possible decimal value that can be represented with N bits.
Decimal | Binary | Normalized
-----------------------------
0 | 000 | 0/7 = 0.000
1 | 001 | 1/7 =~ 0.142
2 | 010 | 2/7 =~ 0.285
3 | 011 | 3/7 =~ 0.428
4 | 100 | 4/7 =~ 0.571
5 | 101 | 5/7 =~ 0.714
6 | 110 | 6/7 =~ 0.857
7 | 111 | 7/7 = 1.000
Going back to the earlier example, where the pixel had bits 1111110100101111, we already know our decimal values for the three color channels: RGB = {31, 41, 15}. We want the normalized values instead, because the decimal values are misleading and don't tell us much without knowing how many bits they were stored in. The red and blue channels are stored with 5 bits, so the largest decimal value is 2^5-1=31; however, the green channel's largest decimal value is 2^6-1=63. Knowing this, the normalized color channels are:
// NormalizedValue = DecimalValue / MaxDecimalValue
R = 31 / 31 = 1.000
G = 41 / 63 =~ 0.650
B = 15 / 31 =~ 0.483
To reiterate, the normalized values are useful because they represent the relative contribution of each color channel in the output. Adding more bits to a given channel doesn't affect the range of possible color, it simply improves color accuracy (more shades of that color channel, basically).
Knowing all of the above, you should be able to convert from any RGB(A) format, regardless of how many bits are stored in each channel, to any other RGB(A) format. For example, let's convert the normalized values we just calculated to B8G8R8A8_UNORM. This is easy once you have normalized values calculated, because you just scale by the maximum value in the new format. Every channel uses 8 bits, so the maximum value is 2^8-1=255. Since the original format didn't have an alpha channel, you would typically just store the max value (meaning fully opaque).
// OutputValue = InputValueNormalized * MaxOutputValue
B = 0.483 * 255 = 123.165
G = 0.650 * 255 = 165.75
R = 1.000 * 255 = 255
A = 1.000 * 255 = 255
There's only one thing missing now before you can code this. Way up above, I was able to pull out the bits for each channel just by lining up them up and copying them. That's how I got the green bits 101001. In code, this can be done by "masking" out the bits we don't care about. Shifting does exactly what it sounds like: it moves bits to the right or left. When you move bits to the right, the rightmost bit gets discarded and the new leftmost bit is assigned 0. Visualization below using the 16 bit example from above.
1111110100101111 // original 16 bits
0111111010010111 // shift right 1x
0011111101001011 // shift right 2x
0001111110100101 // shift right 3x
0000111111010010 // shift right 4x
0000011111101001 // shift right 5x
You can keep shifting, and eventually you'll end up with sixteen 0's. However, I stopped at five shifts for a reason. Notice now the 6 rightmost bits are the green bits (I've shifted/discarded the 5 blue bits). We've very nearly extracted the exact bits we need, but there's still the extra 5 red bits to the left of the green bits. To remove these, we use a "logical and" operation to mask out only the rightmost 6 bits. The mask, in binary, is 0000000000111111; 1 means we want the bit, and 0 means we don't want it. The mask is all 0's except for the last 6 positions, because we only want the last 6 bits. Line this mask up with the 5x shifted number, and the output is 1 when both bits are 1, and 0 for every other bit:
0000011111101001 // original 16 bits shifted 5x to the right
0000000000111111 // bit mask to extract the rightmost 6 bits
------------------------------------------------------------
0000000000101001 // result of the 'logical and' of the two above numbers
The result is exactly the number we're looking for: the 6 green bits and nothing else. Recall that the leading 0's have no effect on the decimal value (it's still 41). It's very simple to do the 'shift right' (>>) and 'logical and' (&) operations in C# or any other C-like language. Here's what it looks like in C#:
// 0xFD2F is 1111110100101111 in binary
uint pixel = 0xFD2F;
// 0x1F is 00011111 in binary (5 rightmost bits are 1)
uint mask5bits = 0x1F;
// 0x3F is 00111111 in binary (6 rightmost bits are 1)
uint mask6bits = 0x3F;
// shift right 11x (discard 5 blue + 6 green bits), then mask 5 bits
uint red = (pixel >> 11) & mask5bits;
// shift right 5x (discard 5 blue bits), then mask 6 bits
uint green = (pixel >> 5) & mask6bits;
// mask 5 rightmost bits
uint blue = pixel & mask5bits;
Putting it all together, you might end up with a routine that looks similar to this. Do be careful to read up on endianness, however, to make sure the bytes are ordered in the way you expect. In this case, the parameter is a 32-bit unsigned integer (first 16 bits ignored)
byte[] R5G6B5toR8G8B8A8(UInt16 input)
{
return new byte[]
{
(byte)((input & 0x1F) / 31.0f * 255), // blue
(byte)(((input >> 5) & 0x3F) / 63.0f * 255), // green
(byte)(((input >> 11) & 0x1F) / 31.0f * 255), // red
255 // alpha
};
}
here is my scenario:
I'm using color.fromargb in C# to get a color out of an int upto 800 (which can be reversed by toargb).
I'm setting a certain pixel( eg: 0,0) in a bitmap to that color and saving it as jpg and when using getpixel to get that color and that int back, I receive a negative value which has puzzled me.
any thoughts and suggestion ?
I suspect two things are at play here.
Firstly JPEG is a lossy format so the number you put in might not be the exact number you get out, a true black is likely to become a gray.
Secondly, why do you get a negative when you start with a positive number? Well this is all down to the way int and uint and colours are represented in binary.
In RGB notation black has hex value #ffffff in ARGB it is #ffffffff.
0xffffffff in hex is 4,294,967,295 in decimal.
However an int is a signed type, meaning it is represented in two's complement representation
If the highest bit in the bit in the number is set then you need to deduct -2,147,483,648; that is, hexadecimal 0x80000000.
So 0xffffffff becomes 2,147,483,647 - 2,147,483,648 = -1.
If your black became a slight gray like #ffeeeeee then its decimal value in two's complement notation would be -2,146,365,166.
I am quite new to image processing and I am looking for a solution to obtain the maximum pixel value of a grayscale image. I am using the Aforge.Net library and I tried using the ImageStatistics class to obtain the maximum pixel value of the grayscale image. I just need to make sure if I am on the correct path. Can someone advise me on the following please?
ImageStatistics stat = new ImageStatistics(bmpSource);
Histogram hist = stat.Gray;
int maxPixelVal = hist.Max;
int minPixelVal = hist.Min;
In this snippet I am getting the maximum value using the GrayChannel. I need to make sure whether this would give me the highest pixel value of the whole image or just the gray channel
Thanks in advance
http://www.aforgenet.com/framework/docs/html/937b609f-8d95-662f-c4ac-55eebc44a1cf.htm
ImageStatistics.Gray Property
Histogram of gray channel.
Remarks:The property is valid only for grayscale images (see IsGrayscale property).
http://www.aforgenet.com/framework/docs/html/61212d57-52ee-9935-2364-b3a34a2213d0.htm
Histogram.Max Property
The property allows to retrieve maximum value of the histogram with non zero hits count.
So, it looks like this would give you what you want.
One thing to consider is the granularity / resolution of the histogram. Sometimes histograms have a number of bins (values) smaller than the number of possible shades in the colour channel it is representing.
Each bin therefore represents a range of shades / pixel values from the source image, and will give you the count of the number of pixels (or the fraction of the total image) in this range.
If this is the case, you will not be able to get the absolute maximum pixel value from the histogram, only the range in which the maximum pixel fell.
Here however the number of bins looks to be 1:1 with the number of possible shades in the colour channel so your approach should work.
How would I go about taking a BMP which is 24 bits and converting it to a Monochrome Bitmap? I am not concerned about losing colors, etc. and I do not want to use another file format.
There are basically two methods. You can use Interop methods to read the raw BMP data and write raw BMP data for a monchrome bitmap. There are googlable functions which will do this.
Or, better, you can use ImageMagickObject to convert the image using a stoichastic dither.
Do the second one.
If you do the first one, you should still use a stocichastic dither, but you will have to implement it by hand.
Edit: You asked "what do the following RGB values become"... the answer is they become what you want them to become. YOU DECIDE.
The obvious choices are to either use a strict threshold, where anything less than X becomes black, anything more becomes white, or you can use a stoichastic dither. Select two thresholds, black Threshold bt and white threshold wt, such that 0 < bt < wt < 255. Then for each point choose a random number q between 0.0. and 1.0. Compare the pixel brightness ((r+g+b)/3) to (q*(wt-bt)+bt). If it is greater or equal, it is white, if less, black. This will give you a nice dithered greyscale. For most purposes 31 and 224 are good values for bt and wt, but for photographic images 0 and 255 might be better.