(english is not my native tongue and it's pretty late).
I am facing a problems for some days and, after many not successfull attemps, I decided to come here to have some help or at least a direction.
We will talk about dithering and color banding.
I will not try to explain here what are these, I will just assume that the reader already knows.
Ok, let's start with a picture
On this image, there is no dithering. Color bands are easy to see, if not on the wall at least on the floor. Because I am generating this image, I do have access to higher precision for each pixel during the generation.
If a channel is an integer from 0 to 255, I do have access to values likes 5.24 (the important thing is ".24" - this is what I mean by a "higher precision").
My original idea was to use a noise based on the pixel's position on screen. If we assert that the noise yield values between 0 and 1, for 5.24 the algorithm would looks like
if ( noise(x,y) < 0.24 ) color = 6
else color = 5
I was also using a "white noise" and I obtained this result
It's a lot smoother, but we get a very high noise. Plus, there is still color banding and this will be the problem I have.
By replacing the white noise by a "blue noise", I get this last image
Which is a lot better for the noisiness of the resulting image but ... there are still color bands.
I tried to figure out why.
I think it's because I use what I would call a "linear split" or "linear gradient" or "linear interpolation" ... or "linear" something, but it's linear.
It the formula I already gave
if ( noise(x,y) < 0.24 ) color = 6
else color = 5
If it's not 0.24 but 0.5, it means a 50/50 mix of (5,5,5) and (6,6,6). However, such and image will appear closer to (6,6,6) than to (5,5,5).
Somehow, I think that it's wrong to think that the step between (0,0,0) and (1,1,1) is the same than the step between (200,200,200) and (201,201,201).
I think it shouldn't be thought as an addition but as a ratio.
And the formula above doesn't really take the "5" into account (it will behave the same way for 5.24 and 200.24).
I don't know if I explain myself well, and it's already really late.
To summerize, I am looking for a formula taking into account
not only the ".24" part
not only the position of the pixel
but also the "5" part.
I think that there is an exponential somewhere (because there is always exponential in optic) and that the result will depend on the monitor (and what I am looking for is more related to cymk than to rgb).
But, still, is there something I can do ? Is there a name to this "non-linear increase of the value of the color" ?
Is there something I can ask to google ?
Thanks a lot.
Related
I'm coming from a Direct3D 9 background, and recently switched my custom game engine over to Direct3D12. After some research, it looked like using one of the *_SRGB formats was the way to go, because it corrected the gamma level.
Immediately, I noticed that everything nearly doubled in brightness, which was unexpected. When correcting a curve, I would expect some values to be brighter and some to be darker, but everything just appears brighter. However, I just accepted it and moved on. But now I'm noticing some other strange issues, and I'm not sure what's going on. Maybe someone could help me understand what I'm missing?
When I draw a primitive with a color value in either HLSL or C++, such as color(128,128,128,255) or float4(0.5,0.5,0.5,1.0), the resulting color I see on the screen is actually RGB 188,188,188. Is this to be expected? I'm reading the values of these colors in Adobe Photoshop 2022, which is in SRGB mode. Should the values not match up if both applications are using SRGB?
128 to 188 is really strange, but 0.5 to 0.73 is even stranger. How do I manually construct a color that comes out the way I constructed it? For example, one might use 0.5 to scale by "half brightness", but 0.73 is definitely not half brightness. It's almost white.
If our textures are painted on a PC, such as in Substance Painter or Photoshop, what is the point of converting all of these colors? If the artist can see the same color space that will be used to render, why tell the display to show us something else?
Before I switched to sRGB, I modeled in Blender, and my textures always looked the same between Blender and my game engine. If I start using sRGB, I'm worried that will not be the case. How are artists making that work?
Images that I've seen that were gamma corrected are often brighter and washed out. And images that were not gamma correct are usually dark and rich. Does gamma correction cause some type of saturation loss in darker color?
I appreciate any guidance. I've done research on this topic, but most of the information goes on endlessly about linear color space. Linear is nice, because it makes math easier, but half of the stuff we deal with in a 3D app is non-linear. At this point, I'm not sure its worth it.
1&2: Gamma correction is designed to convert between light intensity as it exists in the real world, i.e. the amount of photons that hit a camera sensor, and how human beings perceive light. So if a light source is emitting 50% less photons, we see it as as around 74% of the light (individual curves may vary) so 128 should become 188.
3&4: The point of linear color is to allow us to process images in a space where an increase in the number of photons is linearly related to the increase in the intensity values. Then the linear colors are gamma corrected before presenting them to the user. When you work in those programs, you are looking at gamma corrected images.
Basically, people don't look at linear color spaces. They look wrong to us. They only exist to allow the computer to do some processing. If you have shaders that do work in linear color space, saving your images in a linear format so that they don't have to remove the gamma, do the processing, and then reapply the gamma can have performance benefits.
The problem may be that you are gamma correcting images that are already gamma corrected. If the images look right to you, they may be gamma corrected, if they look dark, with the lighter areas seemingly emphasized, they may be linear. If you are adding colors/images that look right to you, before gamma is applied, you will have to put the colors/images through inverse gamma correction.
How Applications Display/Convert Color Spaces: (Edit)
Photoshop interprets what the numbers in an image mean through the currently applied color space. It is possible to both "assign" a color space, which changes how photoshop interprets the numbers, and "convert" a color space, which changes the numbers so that they look the same (or as close as possible) when interpreted through the new color space.
This first image is in the sRGB color space. I've painted a gray dot with the values of (127,127,127).
In this second image I have converted the image to a linear color space. It looks almost the same, because photoshop always applies gamma correction so that it looks right to you, but the first dot now has the value (54,54,54). I've added a second dot with the values (127,127,127) in this color space.
In this third image, I have assigned the sRGB color space. Now photoshop thinks the numbers are in the sRGB color space, so it thinks the image already has gamma correction, and is showing us something like the way linear color space looks.
For the final image, I did everything the opposite direction, drawing a dot with a value of (127,127,127), then converting back. The last dot now has a value of (187,187,187)
Anyone who frequently does UI likely knows that for a given color hsl(H, 100%, 50%) (syntax is CSS) not all values of H will produce a color suitable to be placed under arbitrarily black or white text. The specific fact I'm noting is that certain colors (green) appear especially bright and other (blue) appear especially dark.
Well suppose I would like a user to be able to enter a color hue and have the color always appear with a consistent brightness so that one of either white or black text is guaranteed to always be readable on top of it. I would like all colors to also maintain the most vivid level of saturation they can given the constraint on brightness.
Here is a quick example of what I've tried so far. I start with a grid of squared like this rendered using a bunch of html div elements. Essentially these are hue values roughly from 0 to 360 along the horizontal axis and lightness values from roughly 0% to 100% along the vertical axis. All saturation value are set to 100%.
Using a JS library library called chroma.js, I now process all colors using the color.luminance function, whose definition seems to be to do what I'm looking for. I just passed the lightness of the hsl value in as the parameter to the function. I don't know for sure that this is the best way to accomplish my goal though since I'm not familiar with all the terminology at play here. Please note that my choice to use this library is by no means a constraint on how I want to go about this. It just represents my attempt at solving the problem.
The colors certainly now have a more consistent lightness, but the spectrum now seems particularly vivid around the orange to cyan area and particularly dull everywhere else. Also the colors seems to drop very quickly away from black at the top.
Hopefully this example helps a bit to express what I'm trying to accomplish here. Does any know what they best way to go about this is?
I found the solution! Check out HSLuv. It balances out all the hues in the spectrum so that at any given saturation and lightness, all hues will have the exact same perceived brightness to the human eye.
This solved my problem because now I can just set my text color to white (for example) and then as long as the text is readable against a certain HSLuv lightness it is guaranteed that it will be readable against any hue and saturation used in combination with that lightness. Magic.
I'm working on a 2D game using SDL. One thing that keeps bothering me is that all objects jiggle a bit in relation to each other when the camera is moving slowly.
This is because SDL doesn't support sub pixel positioning, I have to use integers for positioning things on screen.
Here's a quick example of the problem, in case it's not obvious yet. Say I have a side scrolling game where every entity has only an x position:
Entity positions:
friend.x = 0.1
foe.x = 0.3
What's rendered:
friend.x = 0
foe.x = 0
Now let's say the camera is moved to the left by 0.2:
Entity positions:
friend.x = 0.1 + 0.2 = 0.3
foe.x = 0.3 + 0.2 = 0.5
What's rendered:
friend.x = 0
foe.x = 1
There, it jiggles. Both should have moved minimally, but what's rendered is that friend didn't move at all, and foe jumped a whole pixel to the right. When the camera moves another 0.2 to the left, friend will do the jump and foe will stay still. Unfortunately it's actually quite noticeable in practice at low speeds.
The easiest way to mitigate this seemed to be to work with integer positions internally, but I can't really get that to work for my game, just doesn't play well with the physics. I've tried various rounding methods in desperation, but that doesn't seem to change anything in practice. There's still always this jump point when rounding.
So, is there anything I can do to reduce the jiggling? Assuming I'm stuck with both floating point positions and having to work with integers when rendering?
SDL isn't to blame - monitor don't support subpixel positions because, hey, pixel definition. In some rare cases you can use pixel components (dots) separately, but this requires knowledge of actual monitor (different colour orders for different monitors, like RGB, BGR, vertical RGB, etc.); this is how subpixel font antialising works. But font is just one colour, on actual image it would produce insane results.
Only adequate solution I see is increasing resolution. Either resolution of monitor, or by rendering image into higher resolution render target and then downsampling it back to monitor resolution with some filtering.
My original question
I read that to convert a RGB pixel into greyscale RGB, one should use
r_new = g_new = b_new = r_old * 0.3 + g_old * 0.59 + b_old * 0.11
I also read, and understand, that g has a higher weighting because the human eye is more sensitive to green. Implementing that, I saw the results were the same as I would get from setting an image to 'greyscale' in an image editor like the Gimp.
Before I read this, I imagined that to convert a pixel to greyscale, one would convert it to HSL or HSV, then set the saturation to zero (hence, removing all colour). However, when I did this, I got a quite different image output, even though it also lacked colour.
How does s = 0 exactly differ from the 'correct' way I read, and why is it 'incorrect'?
Ongoing findings based on answers and other research
It appears that which luminance coefficients to use is the subject of some debate. Various combinations and to-greyscale algorithms have different results. The following are some presets used in areas like TV standards:
the coefficients defined by ITU-R BT.601 (NTSC?) are 0.299r + 0.587g + 0.114b
the coefficients defined by ITU-R BT.709 (newer) are 0.2126r + 0.7152g + 0.0722b
the coefficients of equal thirds, (1/3)(rgb), is equivalent to s = 0
This scientific article details various greyscale techniques and their results for various images, plus subjective survey of 119 people.
However, when converting an image to greyscale, to achieve the 'best' artistic effect, one will almost certainly not be using these predefined coefficients, but tweaking the contribution from each channel to produce the best output for the particular image.
Although these transformation coefficients exist, nothing binds you to using them. As long as the total intensity of each pixel is unchanged, the contributions from each channel can be anything, ranging from 0 to 100%.
Photographers converting images to grayscale use channel mixers to adjust levels of each channel (RGB or CMYK). In your image, there are many reds and greens, so it might be desirable (depending on your intent) to have those channels more highly represented in the gray level intensity than the blue.
This is what distinguishes "scientific" transformation of the image from an "artistic" combination of the bands.
An additional consideration is the dynamic range of values in each band, and attempting to preserve them in the grayscale image. Boosting shadows and/or highlights might require increasing the contribution of the blue band, for example.
An interesting article on the topic here.... "because human eyes don't detect brightness linearly with color".
http://www.scantips.com/lumin.html
Looks like these coefficients come from old CRT technology and are not well adapted to today's monitors, from the Color FAQ:
The coefficients 0.299, 0.587 and
0.114 properly computed luminance for monitors having phosphors that were
contemporary at the introduction of
NTSC television in 1953. They are
still appropriate for computing video
luma to be discussed below in section
11. However, these coefficients do not accurately compute luminance for
contemporary monitors.
Couldn't find the right conversion coefficient, however.
See also RGB to monochrome conversion
Using s = 0 in HSL/HSV and converting to RGB results in R = G = B, so is the same as doing r_old * 1/3 + g_old * 1/3 + b_old * 1/3.
To understand why, have a look at the Wikipedia page that describes conversion HSV->RGB. Saturation s will be 0, so C and X will be, too. You'll end up with R_1,G_1,B_1 being (0,0,0) and then add m to the final RGB values which results in (m,m,m) = (V,V,V). Same for HSL, result will be (m,m,m) = (L,L,L).
EDIT: OK, just figured out the above is not the complete answer, although it's a good starting point. RGB values will be all the same, either L or V, but it still depends on how L and V were originally calculated, again, see Wikipedia. Seems the program/formulas you've used for converting used the 1/3 * R + 1/3 * G + 1/3 * B solution or one of the other two (hexcone/bi-hexcone).
So after all, using HSL/HSV just means you'll have to decide which formula to use earlier and conversion to RGB grayscale values later is just isolating the last component.
I'm trying to create a spectral image with a constant grey-scale value for every row. I've written some fantastically slow code that basically tries 1000 different variation between black and white for a given hue and it finds the one whose grey-scale value most closely approximates the target value, resulting in the following image:
On my laptop screen (HP) there is a very noticeable 'dip' near the blue peak, where blue pixels near the bottom of the image appear much brighter than the neighbouring purple and cyan pixels. On my second screen (Acer, which has far superior colour display) the dip is smaller, but still there.
I use the following function to compute the grey-scale approximation of a colour:
Math.Abs(targetGrey - (0.2989 * R + 0.5870 * G + 0.1140 * B))
when I convert the image to grey-scale using Paint.NET, I get a perfect black to white gradient, so that part of the code at least works.
So, question: Is this purely an artefact of the display qualities of my screens? Or can the above mentioned grey-scale algorithm be improved upon to give a visually more consistent result?
EDIT: The problem seems to be mostly monitor calibration. Not, I repeat not, a problem with the code.
I'm wondering if its more to do with the way our eyes interpret the colors, rather than screen artifacts.
That said... I am using a very-high quality screen (Dell Ultrasharp, IPS) that has incredible color reproduction and I'm not sure what you mean by "dip" in the blue peak. So either I'm just not noticing it, or my screen doesn't show the same picture and it more color-accurate.
The output looks correct given the greyscale conversion you have used (which I believe is the standard one for sRGB colour spaces).
However - there are lots of tradeoffs in colour models and one of these is that you can get results which aren't visually quite what you want. In your case, the fact that there is a very low blue weight means that a greater amount of blue is needed to get any given greyscale value, hence the blue seems to start lower, at least in terms of how the human eye perceives it.
If your objective is to get a visually appealing spectral image, then I'd suggest altering your function to make the R,G,B weights more equal, and see if you like what you get.