Class: IsDark

Inherits:
Object
  • Object
show all
Defined in:
lib/is_dark.rb

Overview

The ‘Is Dark` class is designed to determine whether a given color or pixel is dark based on luminance standards defined by the W3C. It utilizes the `rmagick` library to handle image processing tasks. The class includes methods to analyze colors from hexadecimal values, individual pixels, and areas within images blobs. It also supports debugging features to visualize the analysis process.

Key features include:

  • **Color Analysis**: Determines if a color is dark based on its luminance.

  • **- **Pixel Analysis**: Analysis individual pixels from an image.

  • **Area Analysis**: Evaluates the darkness of a specified area within an image blob.

  • Debugging: Provides options to enable debugging information and visualize the analysis on a PDF file.

Constant Summary collapse

BLUE_LUMINANCE_COEFFICIENT =
0.0722
GREEN_LUMINANCE_COEFFICIENT =
0.7152
HIGH_LUMINANCE_DIVIDER =
1.055
HIGH_LUMINANCE_POWER =
2.4
LOW_LUMINANCE_DIVIDER =
12.92
LUMINANCE_THRESHOLD =
0.05
MAXIMUM_COLOR_DEPTH =
255
MAX_COLOR_VALUE_MULTIPLIER =
655
MAX_COLOR_VALUE =
MAX_COLOR_VALUE_MULTIPLIER * MAXIMUM_COLOR_DEPTH
LINEAR_LUMINANCE_THRESHOLD =
(1 / (LOW_LUMINANCE_DIVIDER * 100.0)) * MAXIMUM_COLOR_DEPTH
NONLINEAR_TRANSFORM_DIVIDER =
1.055
NONLINEAR_TRANSFORM_OFFSET =
0.055
RED_LUMINANCE_COEFFICIENT =
0.2126
DEFAULT_PERCENT_OF_DOTS =
80
DEFAULT_MATRIX_RANGE =
(0..10).freeze
DEFAULT_DEBUG_FILE_PATH =
'/tmp/is_dark_debug_file.pdf'

Instance Method Summary collapse

Constructor Details

#initialize(settings = {}) ⇒ IsDark

Returns a new instance of IsDark.



45
46
47
# File 'lib/is_dark.rb', line 45

def initialize(settings = {})
  configure(settings)
end

Instance Method Details

#color(hex) ⇒ Object



59
60
61
62
63
# File 'lib/is_dark.rb', line 59

def color(hex)
  @r, @g, @b = hex.match(/^#(..)(..)(..)$/).captures.map(&:hex)
  @colorset = MAXIMUM_COLOR_DEPTH
  dark?
end

#configure(settings = {}) ⇒ Object



49
50
51
52
53
54
55
56
57
# File 'lib/is_dark.rb', line 49

def configure(settings = {})
  @percent = settings[:percent] || DEFAULT_PERCENT_OF_DOTS
  @matrix = settings[:matrix] || DEFAULT_MATRIX_RANGE
  @luminance = settings[:luminance] || LUMINANCE_THRESHOLD
  @with_not_detected_as_white = settings[:with_not_detected_as_white] || true
  @with_debug = settings[:with_debug] || false
  @with_debug_file = settings[:with_debug_file] || false
  @debug_file_path = settings[:debug_file_path] || DEFAULT_DEBUG_FILE_PATH
end

#dark?(x = nil, y = nil) ⇒ Boolean

detects a dark color based on luminance W3 standarts ( www.w3.org/TR/WCAG20/#relativeluminancedef )

Returns:

  • (Boolean)


135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
# File 'lib/is_dark.rb', line 135

def dark?(x = nil, y = nil)
  dark = false
  inverted = false
  pixel = [@r.to_f, @g.to_f, @b.to_f]
  return true if pixel == [0.00, 0.00, 0.00] # hardcoded exception

  if @with_not_detected_as_white && pixel[0] == 0.0 && pixel[1] == 0.0 && pixel[2] == 0.0
    pixel = [MAXIMUM_COLOR_DEPTH, MAXIMUM_COLOR_DEPTH, MAXIMUM_COLOR_DEPTH]
    inverted = true
  end
  calculated = []
  pixel.each do |color|
    color /= @colorset
    if color <= LINEAR_LUMINANCE_THRESHOLD
      color /= LOW_LUMINANCE_DIVIDER
    else
      color = ((color + NONLINEAR_TRANSFORM_OFFSET) / NONLINEAR_TRANSFORM_DIVIDER)**HIGH_LUMINANCE_POWER
    end
    calculated << color
  end
  l = (RED_LUMINANCE_COEFFICIENT * calculated[0]) +
      (GREEN_LUMINANCE_COEFFICIENT * calculated[1]) +
      (BLUE_LUMINANCE_COEFFICIENT * calculated[2])
  dark = true if l <= @luminance
  if @with_debug
    debug = { X: x, Y: y, R: @r, G: @g, B: @b, 'luminance value': l, dark?: dark,
              'inverted to white': inverted }
    p debug
  end
  dark
end

#draw_debug_files(image, x, y, old_x, old_y) ⇒ Object



124
125
126
127
128
129
130
131
132
# File 'lib/is_dark.rb', line 124

def draw_debug_files(image, x, y, old_x, old_y)
  return unless old_x && old_y

  gc = Magick::Draw.new
  gc.line(x, y, old_x, old_y)
  gc.stroke('black')
  gc.draw(image)
  image.write(@debug_file_path)
end

#magick_area_from_blob(x, y, blob, height, width) ⇒ Object

(x, y) is the left corner of an element over a blob, height and width is the element’s size



80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
# File 'lib/is_dark.rb', line 80

def magick_area_from_blob(x, y, blob, height, width)
  image = Magick::Image.read(blob).first
  dark = false
  dots = []
  @matrix.each do |xx|
    @matrix.each do |yy|
      dots << { x: (x + (width * xx / @matrix.count)).to_i, y: (y + (height * yy / @matrix.count)).to_i }
    end
  end

  points = 0
  if @with_debug_file
    old_x = false
    old_y = false
  end
  p '==================================================================================' if @with_debug
  dots.each do |dot|
    x = dot[:x].to_i
    y = dot[:y].to_i
    pix = image.pixel_color(x, y)
    l = magick_pixel(pix, x, y)
    points += 1 if l
    next unless @with_debug_file

    draw_debug_files(image, x, y, old_x, old_y)
    old_y = y
    old_x = x
  end
  dark = true if points >= (dots.length / 100) * @percent
  if @with_debug
    percent_calculated = points/(dots.length / 100)
    p '=================================================================================='
    p "Total Points: #{dots.length}, dark points amount:#{points}"
    p "Is \"invert to white not detectd pixels\" option enabled?:#{@with_not_detected_as_white}"
    p "Percent of dark dots in the matrix: #{percent_calculated}%"
    p "Percent to consider as a dark area from settings: #{@percent}%"
    p "Luminance value is: #{@luminance}"
    p "Is Area Dark?: #{dark}"
    p "have a look on #{@debug_file_path} file to see your tested area of a blob" if @with_debug_file
    p '=================================================================================='
  end
  dark
end

#magick_pixel(pix, x = nil, y = nil) ⇒ Object



65
66
67
68
69
70
71
# File 'lib/is_dark.rb', line 65

def magick_pixel(pix, x = nil, y = nil)
  @r = pix.red.to_f
  @g = pix.green.to_f
  @b = pix.blue.to_f
  @colorset = MAX_COLOR_VALUE
  dark?(x, y)
end

#magick_pixel_from_blob(x, y, blob) ⇒ Object



73
74
75
76
77
# File 'lib/is_dark.rb', line 73

def magick_pixel_from_blob(x, y, blob)
  image = Magick::Image.read(blob).first
  pix = image.pixel_color(x, y)
  magick_pixel(pix, x, y)
end