g-spot-michael 0.0.0

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/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: