Home » Blog » Tutorials » Algorithms for RGB to Grayscale conversion

Algorithms for RGB to Grayscale conversion

This article looks at various algorithms by which we can convert an RGB image to grayscale color. Gray color space is a common image representation that finds application in many graphics tools and tasks.

What is the meaning of grayscale image?

In computer graphics, a grayscale image contains single component pixels that represent the amount of light in the scene. This is the most compact representation of an image since we replace the RGB channels with only one component. In this way, we get a monochrome picture that contains enough information for its perception.

As with RGB, the typical range for gray-scale values is [0-255] or [0-1].

Grayscale Gradient Image
Example grayscale gradient image

Why do we convert RGB to grayscale?

In many cases, we convert RGB to gray-scale simply to achieve an interesting photo effect. However, the main reason for converting RGB to grayscale is to remove color redundancy and obtain a compact and optimal image representation.

Many image processing algorithms work best on a single-channel color format. Moving to a monochrome component means fewer calculations and faster graphics algorithms.

Ho to convert RGB to grayscale?

Now we can move forward with some of the most popular RGB to grayscale algorithms. We will list various conversion formulas along with short code snippets in the most popular programming languages such as: JavaScript, C/C++, GLSL and even in SVG filters. There is no perfect solution, so we will refer to various mathematical formulas for luminance from known color space conversion techniques.

The Average algorithm

The average algorithm is simple and the most popular one. It takes the arithmetic mean of the red, green, and blue color components to perform a conversion for each pixel in the image. The main disadvantage of this approach is that it doesn’t take into account the different color perception of the human eye. The pseudo code of the formula looks like this:

Gray = (R + G + B) / 3

JavaScript Source Code

After reading an image into a Canvas element, we can proceed with the color conversion.

function rgb_to_gray(canvas){
  const context = canvas.getContext("2d");
  const imageData = context.getImageData(0, 0, canvas.width, canvas.height);
  const data = imageData.data;
  for (let i = 0; i < data.length; i+=4){
    const gray = ~~((data[i] + data[i + 1] + data[i + 2]) / 3);
    data[i] = data[i + 1] = data[i + 2] = gray;
  }
  context.putImageData(imageData, 0, 0);
}

The C/C++ Source Code

The example snippet below shows how to convert RGB to gray using C++ code. For simplicity, we use our Image helper class to wrap std::vector as an image object.

Assuming the size of each pixel is 24 bits (3 bytes) RGB, then the conversion code might look like this:

typedef TImage<uint8_t, std::vector<uint8_t>> Image;
...
void rgb_to_gray(Image& image)
{
  for (size_t i = 0; i < image.GetSize(); i += 3)
  {
    const uint8_t value = (image[i + 0] + image[i + 1] + image[i + 2]) / 3;
    image[i + 0] = image[i + 1] = image[i + 2] = value;
  }
}

GLSL (WebGL/OpenGL) Source Code

We can use hardware accelerated conversion using GPU. The sample snippet below lists how to do this using OpenGL Shading Language (GLSL).

precision mediump float;
uniform sampler2D u_image;
uniform vec2 u_textureSize;
void main()
{
  vec4 p = texture2D(u_image, gl_FragCoord.xy / u_textureSize);
  gl_FragColor = vec4(vec3(dot(p.rgb,vec3(1.)) / 3.), p.a);
}

Grayscale Conversion in SVG

The SVG filters are common tool in web development. They are a convenient choice in many cases and even work for a Canvas element.


<svg width="640" height="480" viewBox="0 0 640 480">
<defs>
    <filter id="grayscale">
      <feColorMatrix in="SourceGraphic"
        type="matrix"
        values="0.33 0.33 0.33 0 0
                0.33 0.33 0.33 0 0
                0.33 0.33 0.33 0 0
                0 0 0 1 0" />
   </filter>
  </defs>
  <image x="5" y="5" width="280" height="350" preserveAspectRatio="true" xlink:href="https://upload.wikimedia.org/wikipedia/zh/3/34/Lenna.jpg"/>
  <image x="310" y="5" width="280" height="350" preserveAspectRatio="true" filter="url(#grayscale)" xlink:href="https://upload.wikimedia.org/wikipedia/zh/3/34/Lenna.jpg"/>
</svg>​

The Maximum component (HSV)

This algorithm uses the math behind the Value component of the HSV color space. The formula finds the maximum of the red, green, and blue channels and uses that as the grayscale value.

Gray = max(R, G, B)

JavaScript Source code

function rgb_to_gray(canvas){
  const context = canvas.getContext("2d");
  const imageData = context.getImageData(0, 0, canvas.width, canvas.height);
  const data = imageData.data;
  for (let i = 0; i < data.length; i+=4){
    const gray = Math.max(data[i], data[i + 1], data[i + 2]);
    data[i] = data[i + 1] = data[i + 2] = gray;
  }
  context.putImageData(imageData, 0, 0);
}

C/C++ Source code


typedef TImage<uint8_t, std::vector<uint8_t>> Image;
...
void rgb_to_gray_max(Image& image)
{
  for (size_t i = 0; i < image.GetSize(); i += 3)
  {
    const uint8_t value = std::max(std::max(image[i + 0], image[i + 1]), image[i + 2]);
    image[i + 0] = image[i + 1] = image[i + 2] = value;
  }
}

GLSL conversion

precision mediump float;
uniform sampler2D u_image;
uniform vec2 u_textureSize;
void main()
{
  vec4 p = texture2D(u_image, gl_FragCoord.xy / u_textureSize);
  gl_FragColor = vec4(vec3(max(max(p.r, p.g), p.b)), p.a);
}

Calculating Grayscale by Middle value (HSL)

To calculate Grayscale, we can use the Lightness component of the HSL (Hue, Saturation, Lightness) color space.

Gray = (max(R, G, B) + min(R, G, B)) / 2

JavaScript Source Code

function rgb_to_gray(canvas){
  const context = canvas.getContext("2d");
  const imageData = context.getImageData(0, 0, canvas.width, canvas.height);
  const data = imageData.data;
  for (let i = 0; i < data.length; i+=4){
    const grayMax = Math.max(data[i], data[i + 1], data[i + 2]);
    const grayMin = Math.min(data[i], data[i + 1], data[i + 2]);
    data[i] = data[i + 1] = data[i + 2] = ((grayMax + grayMin) >> 1);
  }
  context.putImageData(imageData, 0, 0);
}

C/C++ Source code


typedef TImage<uint8_t, std::vector<uint8_t>> Image;
...
void rgb_to_gray_max_min(Image& image)
{
  for (size_t i = 0; i < image.GetSize(); i += 3)
  {
    const uint8_t valueMax = std::max(std::max(image[i + 0], image[i + 1]), image[i + 2]);
    const uint8_t valueMin = std::min(std::min(image[i + 0], image[i + 1]), image[i + 2]);
    image[i + 0] = image[i + 1] = image[i + 2] = (valueMax + valueMin) >> 1; // *0.5
  }
}

GLSL Source code

precision mediump float;
uniform sampler2D u_image;
uniform vec2 u_textureSize;
void main()
{
  vec4 p = texture2D(u_image, gl_FragCoord.xy / u_textureSize);
  float maxValue = max(max(p.r, p.g), p.b);
  float minValue = min(min(p.r, p.g), p.b);
  gl_FragColor = vec4(vec3((maxValue + minValue)*0.5), p.a);
}

Grayscale from Luma

This approach provides perceptually relevant results and can sometimes be more optimal than some of the other algorithms in terms of computing power. This grayscale conversion takes the Luma from the weighted average of the Red, Green, and Blue color components. Different standards define different coefficients, but one of the most popular is the following:

Gray = R*0.2989 + G*0.587 + B*0.114

JavaScript code

function rgb_to_gray(canvas){
  const context = canvas.getContext("2d");
  const imageData = context.getImageData(0, 0, canvas.width, canvas.height);
  const data = imageData.data;
  for (let i = 0; i < data.length; i+=4){
    // Use the Y component from YUV for grayscale (SDTV)
    const gray = data[i]*0.2989 + data[i + 1]*0.587 + data[i + 2]*0.114;
    data[i] = data[i + 1] = data[i + 2] = gray;
  }
  context.putImageData(imageData, 0, 0);
}

C++ Source code

void rgb_to_gray_luma(Image& image)
{
  for (size_t i = 0; i < image.GetSize(); i += 3)
  {
    const double value = image[i + 0] * 0.2989 + 
                         image[i + 1] * 0.587 + 
                         image[i + 2] * 0.114;
    image[i + 0] = image[i + 1] = image[i + 2] = static_cast<uint8_t>(value);
  }
}

GLSL Source code

precision mediump float;
uniform sampler2D u_image;
uniform vec2 u_textureSize;
vec4 scale = vec4(0.2989, 0.587, 0.114, 0.0);
void main()
{
  vec4 p = texture2D(u_image, gl_FragCoord.xy / u_textureSize);
  gl_FragColor = vec4(vec3(dot(p,scale)), p.a);
}

Example Results

Finally, let’s see some sample images as a result of these RGB to gray conversion methods.

RGB to grayscale conversion result
Example result from RGB to Gray-scale color conversion

Conclusion

This article provides an overview of various algorithms for converting colors from RGB to grayscale. The tutorial is accompanied by short code snippets in different programming languages like: JavaScript, C/C++, GLSL (WebGL/OpenGL). These RGB to monochrome image conversion techniques are widely used in many graphics apps and algorithms.

Smart RGB to Gray

The following imaging tool can convert an RGB color image to monochrome while preserving a given color shade.

WEB APPLET

Image color effects in the browser!

Smart RGB to Grayscale Converter - Fiveko

You can support me at ko-fi.com