Creating a color-based image search engine using Ruby

People often say a picture speaks volumes, and in many ways, colors are the words that make up those pictures. Colors are woven into the fabric of our lives, and their significance is undeniable.

Have you ever examined an image and tried to pinpoint a specific color? We’ve all done it casually, but never with true precision. When asked to identify colors in an image, we usually rely on common names like red, blue, or green. However, if asked to extract the 30 most dominant colors, our eyes struggle to differentiate and label them accurately. That’s where Camalian comes in. This tool helps you extract colors from images and then manipulate them in various ways.

Camalian: Ultiamte Color Picker for Ruby

This article delves into the world of color spaces, explores the capabilities of the Ruby gem Camalian, and demonstrates how to build a basic image search engine that leverages colors for identification and differentiation.

Exploring Color Spaces

Before diving in, let’s establish some fundamental concepts about colors. Images displayed on our screens are essentially two-dimensional arrays of pixels. While image file encoding may differ, the underlying representation after decompression and decoding remains consistent. In this 2D array structure, each pixel in a color image comprises three components: red, green, and blue. Printed images, while also represented on a 2D surface, utilize dots composed of four ink components: cyan, magenta, yellow, and black. These methods, along with others, are known as color spaces. Some widely used color spaces include RGB, CMYK, HSL, and HSV. CMYK is prevalent in printing, while the others are commonly used in digital media.

RGB Color Space

Electronic displays like CRT screens, LCDs, and phone screens produce color using three primary components: red, green, and blue. The human eye perceives millions of colors by stimulating three types of color receptors, analogous to R, G, and B.

Typically, each color component is stored as a byte with values ranging from 0 to 255.

HSL & HSV Color Spaces

Representing the RGB color space on a cube or a color wheel proves challenging due to limitations in accurately displaying millions of colors and their subtle variations.

ruby image search engine

To address this, researchers in the 1970s introduced the HSV (Hue, Saturation, Value) and HSL (Hue, Saturation, Lightness) color spaces. These spaces can be effectively mapped onto a color wheel, simplifying the identification of various color shades.

Camalian: A Ruby Gem for Colors

Camalian is a Ruby gem dedicated to color manipulation. One of its simplest yet powerful functions is identifying distinct colors within an image.

Imagine an image with 15 unique colors.

It’s undoubtedly easier to discern colors from a palette than from the image itself, especially given the complexity of real-world photographs. Extracting these color values programmatically can be quite intricate, which is where Camalian shines. It handles the complexities, allowing you to easily obtain color information from images.

Getting Started

Installing Camalian is a breeze. Simply use the following command:

1
gem install camalian

To utilize the gem within your Ruby script, require it:

1
require 'camalian'

Extracting Colors

To extract colors, load the image into memory and utilize the methods method:

1
2
3
image = Camalian::load( File.join( File.dirname(__FILE__), 'colormap.png') )
colors = image.prominent_colors(15)
puts colors.map(&:to_hex)

This code snippet loads an image named “colormap.png” from the script’s directory and extracts the 15 most prominent colors.

Saving this code as “color_test1.rb” and executing ruby color_test1.rb in the shell should yield an output similar to:

1
["#318578", "#41b53f", "#2560a3", "#359169", "#2154b1", "#4dda15", "#1d48bf", "#1530dc", "#296d94", "#193dcd", "#3da94d", "#45c131", "#3da84e", "#2d7986", "#193cce"]

With just a few lines of code, we’ve extracted 15 colors from the image, a task that would be tedious manually.

Let’s increase the complexity by using a more detailed image:

Running the same script on this image results in:

1
[“#210b03”, “#723209”, “#974d09”, “#ae5d08”, “#c77414”, “#d77f15”, “#ffea54”, “#94651f”, “#b66a15”, “#c25f06”, “#fdd94d”, “#d39a39”, “#efa540”, “#fffffe”, “#fff655”]

Visualizing this array of color values might resemble:

While the palette is diverse, the extracted colors lack a clear pattern. Let’s enhance this by sorting them based on similarity:

1
colors = image.prominent_colors(15).sort_similar_colors

What if we want to focus on lighter colors, specifically those with a lightness value (in HSL) between 0 and 40? Camalian makes this simple:

1
colors = image.prominent_colors(15).light_colors(0, 40)

Building a Color-Based Image Search Engine

Armed with Camalian’s color manipulation prowess, let’s create a rudimentary web application. This application will enable users to upload images, index them by color, and search for them using color criteria. We’ll focus specifically on Camalian’s role and the color-related aspects of the application.

Our application will encompass image uploading, color extraction and storage, and color-based image retrieval.

Here’s a model diagram illustrating the application’s structure:

Each uploaded image is represented by a PortfolioItem object. Unique colors extracted from images are stored as Color objects, and the relationship between images and their colors is managed through the PortfolioColor entity.

Most of the application’s components, such as image upload handling, model creation, and database persistence, are standard Ruby on Rails practices. The color extraction from uploaded images is handled by the following method:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
after_save :extract_colors

private
  def extract_colors
    image = Camalian::load(self.image.path)
    colors = image.prominent_colors(self.color_count.to_i).sort_similar_colors
    colors.each do |color|
      unless c = Color.where(r: color.r, g: color.g, b: color.b).first
        c = Color.create(r: color.r, g: color.g, b: color.b, h: color.h, s: color.s, l: color.l)
      end
      self.colors << c
    end
  end

This method extracts the color palette from an image and saves it to the database, allowing users to define the number of prominent colors to extract during upload.

Upon image submission, a new PortfolioItem is created, and the extract_colors method is triggered when this item is persisted to the database.

To display the color palette on pages, a helper function is used:

1
2
3
4
5
6
7
8
9
module PortfolioItemsHelper
  def print_color_palette(colors)
    color_string = ''
    colors.each do |c|
      color_string += content_tag :span, ' ', style: "display: block; float: left;  width: 35px; height: 35px; background: #{c.to_hex}"
    end
    content_tag :div, color_string.html_safe, style: "display: inline-block;"
  end
end

This function generates a div element containing small square spans. Each span represents a prominent color from the extracted palette, with its background color set accordingly.

Finally, the search functionality leverages mathematical logic:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class PortfolioSearchForm
  include ActiveModel::Model
  attr_accessor :color, :similarity
  validates_presence_of :color, :similarity

  def color_object
    @color_object ||= Camalian::Color.new(self.color)
  end

  def color_range(color, level)
    (color_object.send(color) - level)..(color_object.send(color) + level)
  end

  def colors_by_rgb
    level = self.similarity.to_i * 255 / 100.0
    Color.where(r: color_range(:r, level), g: color_range(:g, level), b: color_range(:b, level))
  end

  def colors_by_hsl
    level = self.similarity.to_i
    Color.where(h: color_range(:h, (self.similarity.to_i * 30 / 100.0) ), s: color_range(:s, level), l: color_range(:l, level))
  end
end

The colors_by_hsl method retrieves all Color entities matching the search criteria. These entities are then used to identify and display relevant images. The query itself is straightforward. Based on a specified color and a similarity value, a range of values is calculated for each color component.

That covers the core elements of our application.

Putting It to the Test

The complete code is available on GitHub. Deploy the application to Heroku or run it locally:

1
2
3
4
5
git clone https://github.com/nazarhussain/camalian-sample-app.git
cd camalian-sample-app
bundle install
rake db:migrate
rails s

The application’s interface should resemble:

Once running, navigate to http://localhost:3000 in your web browser. Upload images with diverse color palettes. Use the search bar at the top right to find images based on color. The threshold dropdown controls the tolerance for color matching.

Exploring Further Possibilities

While this demo application is basic, it showcases the potential of Camalian. Consider these practical applications:

  • Preventing users from uploading dark profile pictures
  • Dynamically adjusting a website’s color theme based on user-uploaded images
  • Automating color palette validation for design submissions

Explore the library further on GitHub and delve into its source code. Contribute by reporting bugs or submitting pull requests.

Licensed under CC BY-NC-SA 4.0