The first attempt

Working on a project where users can customise background colour for their profile, I needed a way to test if a background colour is light or dark in order to choose an appropriate text colour – black on light colours and white on dark ones. My search for a solution began with me calculating the greyscale of colour, but any algorithm I tried produced mixed results that didn’t satisfy my needs.

Then I changed my approach and started to consider RGB colours as points in 3D space where r, g, n are the axes. In this case, black is a point represented by the coordinate (0, 0, 0). A colour is darker the closer it is to this “black” point. So, calculating the brightness of the colour became a question of calculating distance in 3D space, which could be answered with the formula:

sqrt(dx^2+dy^2+dz^2)

or in our case simply,

sqrt(x^2+y^2+z^2)

because black is (0, 0, 0).

Still not right

This solution, however, was not perfect. One of the problems that arose was related to the fact that red, green and blue have different wavelengths and consequently they have three different contributions in the formation of the final colour. The colour red has the longest wavelength of the three colours, while green has a shorter wavelength than red. Not only this, but green is also the colour that produces a more soothing effect on the eyes. This means we have to decrease the contribution of the red colour and increase the contribution of the green colour. In other words, we needed to find a way to adjust colour weight.

The final solution

There is no unique way to choose colour weight, so we decided to use the same algorithm for converting RGB to YIQ colour space used in standard colour TV and video systems in North America. Weights for red, green and blue are 0.299, 0.587 and 0.114. Knowing how to choose the right weight for each colour component required knowledge of photometry, however, this is beyond the scope of this blog.

Combining the colour weights and the distance formula presented earlier, we came up with a final solution. Below is the solution we implemented in ruby:

def brightness(pixel)
 Math.sqrt(
   0.299 * pixel.red**2 +
   0.587 * pixel.green**2 +
   0.114 * pixel.blue**2
 ).to_i
end

Because brightness can be considered as a grayscale, we can use the same formula for converting coloured images into black-and-white. We have to replace each r, g, b component in every pixel with pixel brightness. Below is a ruby implementation and what we got as a result.

require 'RMagick'

def brightness(pixel)
  Math.sqrt(
    0.299 * pixel.red**2 +
    0.587 * pixel.green**2 +
    0.114 * pixel.blue**2
  ).to_i
end

image = Magick::Image.read('bosmal.jpg')[0]

image.each_pixel do |pixel, c, r|
  pixel.red = pixel.green = pixel.blue = brightness(pixel)
  image.pixel_color(c, r, pixel)
end

image.write("gray.jpg")

For reading image from file and saving to file solution using ImageMagick image processing library (check it out here) and RMagick gem (check it out here)

Decorator pattern
Software DevelopmentTech Bites
January 23, 2023

Decorator pattern

Design patterns are typical solutions to common problems in software design. Each pattern is like a blueprint that you can customize to solve a particular design problem in your code. A decorator pattern allows users to add new functionality to an existing object without altering its structure. This design pattern comes under…
JPA annotations in Hibernate
Software DevelopmentTech Bites
January 5, 2023

JPA annotations in Hibernate

Suppose you have wondered how we interact with relational databases without (directly) using SQL queries. In that case, the answer usually lies in Object-Relational Mapping (ORM), which will do the job of converting Java objects and statements to database tables and related queries. Hibernate comes into this story as the…

Leave a Reply