g-spot-michael 0.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (3) hide show
  1. checksums.yaml +7 -0
  2. data/lib/g_spot_michael.rb +305 -0
  3. metadata +46 -0
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 4c41e23eece9dd00540a6de977823c9936e6d9bc
4
+ data.tar.gz: f9abc425f3f47e7c99e610a9dc96296760fd2b19
5
+ SHA512:
6
+ metadata.gz: e52bf05e32a9ff32f29b8f4b05a0029803644b54ed95af4b1d023cc37b811417ebffaf2ee456e14a07e400bed48e129ec9f610a9d6e8c6d32a0af57954535091
7
+ data.tar.gz: b4bead399c6ae4e421b653e02318f4e0059fe02095673fa351f022a6b1264ae2e4b8372f412166a1118d5abb06b070079c4d2aeb80b3315954ec43c3b5f11ad7
@@ -0,0 +1,305 @@
1
+ require 'timeout'
2
+ require 'open3'
3
+
4
+ class GSpotMichael
5
+
6
+ TARGET_DARK_LUMA = 0.26
7
+ MAX_DARK_LUMA = 0.45
8
+ MIN_LIGHT_LUMA = 0.55
9
+ TARGET_LIGHT_LUMA = 0.74
10
+
11
+ MIN_NORMAL_LUMA = 0.3
12
+ TARGET_NORMAL_LUMA = 0.5
13
+ MAX_NORMAL_LUMA = 0.7
14
+
15
+ TARGET_MUTED_SATURATION = 0.3
16
+ MAX_MUTED_SATURATION = 0.4
17
+
18
+ TARGET_VIBRANT_SATURATION = 1
19
+ MIN_VIBRANT_SATURATION = 0.35
20
+
21
+ WEIGHT_SATURATION = 3
22
+ WEIGHT_LUMA = 6
23
+ WEIGHT_POPULATION = 1
24
+
25
+ def initialize(file, colorCount=64, quality='5%')
26
+ @HighestPopulation = 0
27
+
28
+ # we're going to take the original file,
29
+ # reduce the size, quantize the colors,
30
+ # and then return an array of distinct
31
+ # RGBs with their occurence counts(as pairs)
32
+ file.rewind
33
+ results = run_command "convert - -scale '#{quality}' +dither -colors #{colorCount} txt:", file.read.force_encoding("UTF-8")
34
+ pixels = results.split("\n").map{|x| (r = x.match(/srgb\((\d{1,3},\d{1,3},\d{1,3})\)/)) ? r[1] : nil}.compact.map{|p| p.split(",").map(&:to_i)}
35
+ cmap = {}
36
+ pixels.each do |pixel|
37
+ # If pixel is mostly opaque and not white
38
+ if !(pixel[0] > 250 && pixel[1] > 250 && pixel[2] > 250)
39
+ cmap[pixel] ||= 0
40
+ cmap[pixel] += 1
41
+ end
42
+ end
43
+
44
+ @swatches = cmap.map do |vbox|
45
+ Swatch.new vbox[0], vbox[1] # values, count
46
+ end
47
+
48
+ @maxPopulation = find_max_population
49
+ @HighestPopulation = @maxPopulation
50
+
51
+ generate_variation_colors
52
+ generate_empty_swatches
53
+ end
54
+
55
+ def generate_variation_colors
56
+ @VibrantSwatch = find_color_variation(TARGET_NORMAL_LUMA, MIN_NORMAL_LUMA, MAX_NORMAL_LUMA, TARGET_VIBRANT_SATURATION, MIN_VIBRANT_SATURATION, 1)
57
+ @LightVibrantSwatch = find_color_variation(TARGET_LIGHT_LUMA, MIN_LIGHT_LUMA, 1, TARGET_VIBRANT_SATURATION, MIN_VIBRANT_SATURATION, 1)
58
+ @DarkVibrantSwatch = find_color_variation(TARGET_DARK_LUMA, 0, MAX_DARK_LUMA, TARGET_VIBRANT_SATURATION, MIN_VIBRANT_SATURATION, 1)
59
+ @MutedSwatch = find_color_variation(TARGET_NORMAL_LUMA, MIN_NORMAL_LUMA, MAX_NORMAL_LUMA, TARGET_MUTED_SATURATION, 0, MAX_MUTED_SATURATION)
60
+ @LightMutedSwatch = find_color_variation(TARGET_LIGHT_LUMA, MIN_LIGHT_LUMA, 1, TARGET_MUTED_SATURATION, 0, MAX_MUTED_SATURATION)
61
+ @DarkMutedSwatch = find_color_variation(TARGET_DARK_LUMA, 0, MAX_DARK_LUMA, TARGET_MUTED_SATURATION, 0, MAX_MUTED_SATURATION)
62
+ end
63
+
64
+ def generate_empty_swatches
65
+ if !@VibrantSwatch
66
+ # If we do not have a vibrant color...
67
+ if @DarkVibrantSwatch
68
+ # ...but we do have a dark vibrant, generate the value by modifying the luma
69
+ hsl = @DarkVibrantSwatch.hsl
70
+ hsl[2] = TARGET_NORMAL_LUMA
71
+ @VibrantSwatch = Swatch.new hslToRgb(hsl[0], hsl[1], hsl[2]), 0
72
+ end
73
+ end
74
+
75
+ if !@DarkVibrantSwatch
76
+ # If we do not have a vibrant color...
77
+ if @VibrantSwatch
78
+ # ...but we do have a dark vibrant, generate the value by modifying the luma
79
+ hsl = @VibrantSwatch.hsl
80
+ hsl[2] = TARGET_DARK_LUMA
81
+ @DarkVibrantSwatch = Swatch.new hslToRgb(hsl[0], hsl[1], hsl[2]), 0
82
+ end
83
+ end
84
+ end
85
+
86
+ def find_max_population
87
+ population = @swatches.map{|swatch| [0, swatch.population].max }.max
88
+ population
89
+ end
90
+
91
+ def find_color_variation (targetLuma, minLuma, maxLuma, targetSaturation, minSaturation, maxSaturation)
92
+ max = nil
93
+ maxValue = 0
94
+
95
+ @swatches.each do |swatch|
96
+ sat = swatch.hsl[1]
97
+ luma = swatch.hsl[2]
98
+ if !is_already_selected(swatch) && sat >= minSaturation && sat <= maxSaturation && luma >= minLuma && luma <= maxLuma
99
+ value = create_comparison_value sat, targetSaturation, luma, targetLuma, swatch.population, @HighestPopulation
100
+ if !max || value > maxValue
101
+ max = swatch
102
+ maxValue = value
103
+ end
104
+ end
105
+ end
106
+
107
+ max
108
+ end
109
+
110
+ def create_comparison_value(saturation, targetSaturation, luma, targetLuma, population, maxPopulation)
111
+ # pop = (maxPopulation != 0) ? (population / maxPopulation) : 0.0
112
+ weighted_mean(
113
+ invert_diff(saturation, targetSaturation), WEIGHT_SATURATION,
114
+ invert_diff(luma, targetLuma), WEIGHT_LUMA,
115
+ (population / maxPopulation), WEIGHT_POPULATION
116
+ )
117
+ end
118
+
119
+ def invert_diff (value, targetValue)
120
+ 1 - (value - targetValue).abs
121
+ end
122
+
123
+ def weighted_mean(*values)
124
+ sum = 0
125
+ sumWeight = 0
126
+ i = 0
127
+ while i < values.length
128
+ value = values[i]
129
+ weight = values[i + 1]
130
+ sum += value * weight
131
+ sumWeight += weight
132
+ i += 2
133
+ end
134
+ sum / sumWeight
135
+ end
136
+
137
+ def swatches
138
+ {
139
+ Vibrant: @VibrantSwatch,
140
+ Muted: @MutedSwatch,
141
+ DarkVibrant: @DarkVibrantSwatch,
142
+ DarkMuted: @DarkMutedSwatch,
143
+ LightVibrant: @LightVibrantSwatch,
144
+ LightMuted: @LightMuted
145
+ }
146
+ end
147
+
148
+ def is_already_selected(swatch)
149
+ @VibrantSwatch == swatch ||
150
+ @DarkVibrantSwatch == swatch ||
151
+ @LightVibrantSwatch == swatch ||
152
+ @MutedSwatch == swatch ||
153
+ @DarkMutedSwatch == swatch ||
154
+ @LightMutedSwatch == swatch
155
+ end
156
+
157
+ def hslToRgb(h, s, l)
158
+ r = nil
159
+ g = nil
160
+ b = nil
161
+
162
+ hue2rgb = Proc.new { |p, q, t|
163
+ if t < 0
164
+ t += 1
165
+ end
166
+ if t > 1
167
+ t -= 1
168
+ end
169
+ if t < 1 / 6
170
+ return p + (q - p) * 6 * t
171
+ end
172
+ if t < 1 / 2
173
+ return q
174
+ end
175
+ if t < 2 / 3
176
+ return p + (q - p) * (2 / 3 - t) * 6
177
+ end
178
+ p
179
+ }
180
+
181
+ if s == 0
182
+ r = g = b = l
183
+ # achromatic
184
+ else
185
+ q = (l < 0.5) ? (l * (1 + s)) : (l + s - (l * s))
186
+ p = 2 * l - q
187
+ r = hue2rgb.call(p, q, h + 1 / 3)
188
+ g = hue2rgb.call(p, q, h)
189
+ b = hue2rgb.call(p, q, h - (1 / 3))
190
+ end
191
+ [
192
+ r * 255,
193
+ g * 255,
194
+ b * 255
195
+ ]
196
+ end
197
+
198
+ def run_command command, input
199
+ stdin, stdout, stderr, wait_thr = Open3.popen3(command)
200
+ pid = wait_thr.pid
201
+
202
+ Timeout.timeout(10) do # cancel in 10 seconds
203
+ stdin.write input
204
+ stdin.close
205
+
206
+ output_buffer = []
207
+ error_buffer = []
208
+
209
+ while (output_chunk = stdout.gets) || (error_chunk = stderr.gets)
210
+ output_buffer << output_chunk
211
+ error_buffer << error_chunk
212
+ end
213
+
214
+ output_buffer.compact!
215
+ error_buffer.compact!
216
+
217
+ output = output_buffer.any? ? output_buffer.join('') : nil
218
+ error = error_buffer.any? ? error_buffer.join('') : nil
219
+
220
+ unless error
221
+ raise StandardError.new("No output received.") if !output
222
+ return output
223
+ else
224
+ raise StandardError.new(error)
225
+ end
226
+ end
227
+ rescue Timeout::Error, StandardError, Errno::EPIPE => e
228
+ e
229
+ ensure
230
+ begin
231
+ Process.kill("KILL", pid) if pid
232
+ rescue Errno::ESRCH
233
+ # Process is already dead so do nothing.
234
+ end
235
+ stdin = nil
236
+ stdout = nil
237
+ stderr = nil
238
+ wait_thr.value if wait_thr # Process::Status object returned.
239
+ end
240
+
241
+ class Swatch
242
+
243
+ attr_accessor :rgb, :population
244
+
245
+ def initialize(rgb, population=1)
246
+ @rgb = rgb
247
+ @population = population
248
+ @yiq = 0
249
+ end
250
+
251
+ def hsl
252
+ @hsl ||= rgb_to_hsl @rgb[0], @rgb[1], @rgb[2]
253
+ end
254
+
255
+ def hex
256
+ "#" + ((1 << 24) + (@rgb[0] << 16) + (@rgb[1] << 8) + @rgb[2]).to_s(16).slice(1, 7)
257
+ end
258
+
259
+ def get_title_text_color
260
+ ensure_text_colors
261
+ (@yiq < 200) ? "#fff" : "#000"
262
+ end
263
+
264
+ def get_body_text_color
265
+ ensure_text_colors
266
+ (@yiq < 150) ? "#fff" : "#000"
267
+ end
268
+
269
+ private
270
+
271
+ def ensure_text_colors
272
+ if @yiq != 0
273
+ @yiq = (@rgb[0] * 299 + @rgb[1] * 587 + @rgb[2] * 114) / 1000
274
+ end
275
+ end
276
+
277
+ def rgb_to_hsl(r, g, b)
278
+ r = r.to_f / 255.0
279
+ g = g.to_f / 255.0
280
+ b = b.to_f / 255.0
281
+ max = [r, g, b].max
282
+ min = [r, g, b].min
283
+ h = 0.0
284
+ s = 0.0
285
+ l = (max + min) / 2
286
+ unless max == min # if not achromatic
287
+ d = max - min
288
+ s = (l > 0.5) ? (d / (2 - max - min)) : (d / (max + min))
289
+ case max
290
+ when r
291
+ h = (g - b) / d + ((g < b) ? 6 : 0)
292
+ when g
293
+ h = (b - r) / d + 2
294
+ when b
295
+ h = (r - g) / d + 4
296
+ end
297
+ h = h / 6
298
+ end
299
+ [h, s, l]
300
+ end
301
+
302
+ end
303
+
304
+ end
305
+
metadata ADDED
@@ -0,0 +1,46 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: g-spot-michael
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Ben Titcomb
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2017-04-07 00:00:00.000000000 Z
12
+ dependencies: []
13
+ description: A Ruby port of Vibrant.js using Imagemagick. Extracts prominent colors
14
+ from a provided image.
15
+ email: benjamin@pixelstreetinc.com
16
+ executables: []
17
+ extensions: []
18
+ extra_rdoc_files: []
19
+ files:
20
+ - lib/g_spot_michael.rb
21
+ homepage: https://github.com/ravenstine/g-spot-michael
22
+ licenses:
23
+ - MIT
24
+ metadata: {}
25
+ post_install_message:
26
+ rdoc_options: []
27
+ require_paths:
28
+ - lib
29
+ required_ruby_version: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ required_rubygems_version: !ruby/object:Gem::Requirement
35
+ requirements:
36
+ - - ">="
37
+ - !ruby/object:Gem::Version
38
+ version: '0'
39
+ requirements: []
40
+ rubyforge_project:
41
+ rubygems_version: 2.5.1
42
+ signing_key:
43
+ specification_version: 4
44
+ summary: A Ruby port of Vibrant.js using Imagemagick
45
+ test_files: []
46
+ has_rdoc: