is_dark 0.2.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. checksums.yaml +7 -0
  2. data/lib/is_dark.rb +166 -0
  3. metadata +94 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 9dad19d8626d41d0e19a099e5da1439a1159fb484ff836c4c8b3fe42f8f990dd
4
+ data.tar.gz: f456d8b95650357c374a74b5f5909574d7a8b7f5540fed03f5e2652a51bf2530
5
+ SHA512:
6
+ metadata.gz: 414aef11db695594ebf025376724235e489a1d8843e93fec57b1862cff0ea5e68e59cf6f7f7d7082dab30cdf12e1339ab4695ec120294524da632fd4026b1362
7
+ data.tar.gz: 91cdfa5191f92ea826f60ef951df06f5d204d444943ad06b998a3092a7642fbc796824594a090127903135469767eba5a5580ab8098386be29d13b124407ac73
data/lib/is_dark.rb ADDED
@@ -0,0 +1,166 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rmagick'
4
+
5
+ # The `Is Dark` class is designed to determine whether a given color or pixel is dark based on luminance
6
+ # standards defined by the W3C. It utilizes the `rmagick` library to handle image processing tasks.
7
+ # The class includes methods to analyze colors from hexadecimal values, individual pixels,
8
+ # and areas within images blobs. It also supports debugging features to visualize the analysis process.
9
+ #
10
+ # Key features include:
11
+ # - **Color Analysis**: Determines if a color is dark based on its luminance.
12
+ # - **- **Pixel Analysis**: Analysis individual pixels from an image.
13
+ # - **Area Analysis**: Evaluates the darkness of a specified area within an image blob.
14
+ # - **Debugging**: Provides options to enable debugging information and visualize the analysis on a PDF file.
15
+ class IsDark
16
+ BLUE_LUMINANCE_COEFFICIENT = 0.0722
17
+ GREEN_LUMINANCE_COEFFICIENT = 0.7152
18
+ HIGH_LUMINANCE_DIVIDER = 1.055
19
+ HIGH_LUMINANCE_POWER = 2.4
20
+ LOW_LUMINANCE_DIVIDER = 12.92
21
+ LUMINANCE_THRESHOLD = 0.05
22
+ MAXIMUM_COLOR_DEPTH = 255
23
+ MAX_COLOR_VALUE_MULTIPLIER = 655
24
+ MAX_COLOR_VALUE = MAX_COLOR_VALUE_MULTIPLIER * MAXIMUM_COLOR_DEPTH
25
+ LINEAR_LUMINANCE_THRESHOLD = (1 / (LOW_LUMINANCE_DIVIDER * 100.0)) * MAXIMUM_COLOR_DEPTH
26
+ NONLINEAR_TRANSFORM_DIVIDER = 1.055
27
+ NONLINEAR_TRANSFORM_OFFSET = 0.055
28
+ RED_LUMINANCE_COEFFICIENT = 0.2126
29
+ DEFAULT_PERCENT_OF_DOTS = 80
30
+ DEFAULT_MATRIX_RANGE = (0..10).freeze
31
+ DEFAULT_DEBUG_FILE_PATH = '/tmp/is_dark_debug_file.pdf'
32
+
33
+ @r = 0
34
+ @g = 0
35
+ @b = 0
36
+ @colorset = MAXIMUM_COLOR_DEPTH
37
+ @percent = DEFAULT_PERCENT_OF_DOTS
38
+ @matrix = DEFAULT_MATRIX_RANGE
39
+ @luminance = LUMINANCE_THRESHOLD
40
+ @with_not_detected_as_white = true
41
+ @with_debug = false
42
+ @with_debug_file = false
43
+ @debug_file_path = DEFAULT_DEBUG_FILE_PATH
44
+
45
+ def initialize(settings = {})
46
+ configure(settings)
47
+ end
48
+
49
+ def configure(settings = {})
50
+ @percent = settings[:percent] || DEFAULT_PERCENT_OF_DOTS
51
+ @matrix = settings[:matrix] || DEFAULT_MATRIX_RANGE
52
+ @luminance = settings[:luminance] || LUMINANCE_THRESHOLD
53
+ @with_not_detected_as_white = settings[:with_not_detected_as_white] || true
54
+ @with_debug = settings[:with_debug] || false
55
+ @with_debug_file = settings[:with_debug_file] || false
56
+ @debug_file_path = settings[:debug_file_path] || DEFAULT_DEBUG_FILE_PATH
57
+ end
58
+
59
+ def color(hex)
60
+ @r, @g, @b = hex.match(/^#(..)(..)(..)$/).captures.map(&:hex)
61
+ @colorset = MAXIMUM_COLOR_DEPTH
62
+ dark?
63
+ end
64
+
65
+ def magick_pixel(pix, x = nil, y = nil)
66
+ @r = pix.red.to_f
67
+ @g = pix.green.to_f
68
+ @b = pix.blue.to_f
69
+ @colorset = MAX_COLOR_VALUE
70
+ dark?(x, y)
71
+ end
72
+
73
+ def magick_pixel_from_blob(x, y, blob)
74
+ image = Magick::Image.read(blob).first
75
+ pix = image.pixel_color(x, y)
76
+ magick_pixel(pix, x, y)
77
+ end
78
+
79
+ # (x, y) is the left corner of an element over a blob, height and width is the element's size
80
+ def magick_area_from_blob(x, y, blob, height, width)
81
+ image = Magick::Image.read(blob).first
82
+ dark = false
83
+ dots = []
84
+ @matrix.each do |xx|
85
+ @matrix.each do |yy|
86
+ dots << { x: (x + (width * xx / @matrix.count)).to_i, y: (y + (height * yy / @matrix.count)).to_i }
87
+ end
88
+ end
89
+
90
+ points = 0
91
+ if @with_debug_file
92
+ old_x = false
93
+ old_y = false
94
+ end
95
+ p '==================================================================================' if @with_debug
96
+ dots.each do |dot|
97
+ x = dot[:x].to_i
98
+ y = dot[:y].to_i
99
+ pix = image.pixel_color(x, y)
100
+ l = magick_pixel(pix, x, y)
101
+ points += 1 if l
102
+ next unless @with_debug_file
103
+
104
+ draw_debug_files(image, x, y, old_x, old_y)
105
+ old_y = y
106
+ old_x = x
107
+ end
108
+ dark = true if points >= (dots.length / 100) * @percent
109
+ if @with_debug
110
+ percent_calculated = points/(dots.length / 100)
111
+ p '=================================================================================='
112
+ p "Total Points: #{dots.length}, dark points amount:#{points}"
113
+ p "Is \"invert to white not detectd pixels\" option enabled?:#{@with_not_detected_as_white}"
114
+ p "Percent of dark dots in the matrix: #{percent_calculated}%"
115
+ p "Percent to consider as a dark area from settings: #{@percent}%"
116
+ p "Luminance value is: #{@luminance}"
117
+ p "Is Area Dark?: #{dark}"
118
+ p "have a look on #{@debug_file_path} file to see your tested area of a blob" if @with_debug_file
119
+ p '=================================================================================='
120
+ end
121
+ dark
122
+ end
123
+
124
+ def draw_debug_files(image, x, y, old_x, old_y)
125
+ return unless old_x && old_y
126
+
127
+ gc = Magick::Draw.new
128
+ gc.line(x, y, old_x, old_y)
129
+ gc.stroke('black')
130
+ gc.draw(image)
131
+ image.write(@debug_file_path)
132
+ end
133
+
134
+ # detects a dark color based on luminance W3 standarts ( https://www.w3.org/TR/WCAG20/#relativeluminancedef )
135
+ def dark?(x = nil, y = nil)
136
+ dark = false
137
+ inverted = false
138
+ pixel = [@r.to_f, @g.to_f, @b.to_f]
139
+ return true if pixel == [0.00, 0.00, 0.00] # hardcoded exception
140
+
141
+ if @with_not_detected_as_white && pixel[0] == 0.0 && pixel[1] == 0.0 && pixel[2] == 0.0
142
+ pixel = [MAXIMUM_COLOR_DEPTH, MAXIMUM_COLOR_DEPTH, MAXIMUM_COLOR_DEPTH]
143
+ inverted = true
144
+ end
145
+ calculated = []
146
+ pixel.each do |color|
147
+ color /= @colorset
148
+ if color <= LINEAR_LUMINANCE_THRESHOLD
149
+ color /= LOW_LUMINANCE_DIVIDER
150
+ else
151
+ color = ((color + NONLINEAR_TRANSFORM_OFFSET) / NONLINEAR_TRANSFORM_DIVIDER)**HIGH_LUMINANCE_POWER
152
+ end
153
+ calculated << color
154
+ end
155
+ l = (RED_LUMINANCE_COEFFICIENT * calculated[0]) +
156
+ (GREEN_LUMINANCE_COEFFICIENT * calculated[1]) +
157
+ (BLUE_LUMINANCE_COEFFICIENT * calculated[2])
158
+ dark = true if l <= @luminance
159
+ if @with_debug
160
+ debug = { X: x, Y: y, R: @r, G: @g, B: @b, 'luminance value': l, dark?: dark,
161
+ 'inverted to white': inverted }
162
+ p debug
163
+ end
164
+ dark
165
+ end
166
+ end
metadata ADDED
@@ -0,0 +1,94 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: is_dark
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.2.3
5
+ platform: ruby
6
+ authors:
7
+ - Sergei Illarionov
8
+ - Liamshin Ilia
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2025-02-03 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: rmagick
16
+ requirement: !ruby/object:Gem::Requirement
17
+ requirements:
18
+ - - "~>"
19
+ - !ruby/object:Gem::Version
20
+ version: '5.2'
21
+ type: :runtime
22
+ prerelease: false
23
+ version_requirements: !ruby/object:Gem::Requirement
24
+ requirements:
25
+ - - "~>"
26
+ - !ruby/object:Gem::Version
27
+ version: '5.2'
28
+ - !ruby/object:Gem::Dependency
29
+ name: rspec
30
+ requirement: !ruby/object:Gem::Requirement
31
+ requirements:
32
+ - - "~>"
33
+ - !ruby/object:Gem::Version
34
+ version: '3.13'
35
+ type: :development
36
+ prerelease: false
37
+ version_requirements: !ruby/object:Gem::Requirement
38
+ requirements:
39
+ - - "~>"
40
+ - !ruby/object:Gem::Version
41
+ version: '3.13'
42
+ - !ruby/object:Gem::Dependency
43
+ name: rubocop
44
+ requirement: !ruby/object:Gem::Requirement
45
+ requirements:
46
+ - - "~>"
47
+ - !ruby/object:Gem::Version
48
+ version: '1.69'
49
+ type: :development
50
+ prerelease: false
51
+ version_requirements: !ruby/object:Gem::Requirement
52
+ requirements:
53
+ - - "~>"
54
+ - !ruby/object:Gem::Version
55
+ version: '1.69'
56
+ description: "Detects a dark color based on luminance W3 standarts ( https://www.w3.org/TR/WCAG20/#relativeluminancedef
57
+ ). \n\n It has these options: \n * is a hex color dark \n * is an Imagick pixel
58
+ dark \n * is an Imagick pixel from a blob dark \n * is an area in a blob over a
59
+ dark background (uses Imagick for it too). \n\n An example practical aspect: it
60
+ can be useful to understand will a black colored text be visible or not over an
61
+ area."
62
+ email: butteff.ru@gmail.com
63
+ executables: []
64
+ extensions: []
65
+ extra_rdoc_files: []
66
+ files:
67
+ - lib/is_dark.rb
68
+ homepage: https://butteff.ru/en/
69
+ licenses:
70
+ - MIT
71
+ metadata:
72
+ source_code_uri: https://github.com/butteff/is_dark_ruby_gem
73
+ rubygems_mfa_required: 'true'
74
+ post_install_message: You can find docs about is_dark gem on https://butteff.ru/en/extensions
75
+ or on https://github.com/butteff/is_dark_ruby_gem
76
+ rdoc_options: []
77
+ require_paths:
78
+ - lib
79
+ required_ruby_version: !ruby/object:Gem::Requirement
80
+ requirements:
81
+ - - ">="
82
+ - !ruby/object:Gem::Version
83
+ version: 2.7.0
84
+ required_rubygems_version: !ruby/object:Gem::Requirement
85
+ requirements:
86
+ - - ">="
87
+ - !ruby/object:Gem::Version
88
+ version: '0'
89
+ requirements: []
90
+ rubygems_version: 3.4.19
91
+ signing_key:
92
+ specification_version: 4
93
+ summary: Detects a dark background under an area or by a color code
94
+ test_files: []