Paletti 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (5) hide show
  1. checksums.yaml +7 -0
  2. data/bin/pal +29 -0
  3. data/lib/paletti.rb +71 -0
  4. data/lib/paletti/pixel.rb +91 -0
  5. metadata +49 -0
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 9b4eb61346249f243156a63eb8a889be54807e27
4
+ data.tar.gz: fa3dce231ec9e9503f4245849bc65808d5fb0fda
5
+ SHA512:
6
+ metadata.gz: ff5b42b80f6fbb52ae401ff39a6ebad599725aa470bc6bc56ebedae3578a3cea7b24add8e09909a53a722a0cbd2299f6ce67cbd5cbd69638ef58e5fc49c61eaa
7
+ data.tar.gz: 51948fca59a4540d08b3a31fb37606de4286fed95bf9d0152a01d4aae04a75011d129893ef0dd84ef186d531ca24bdeeccdace130ade9d723314211abc7d063e
data/bin/pal ADDED
@@ -0,0 +1,29 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'paletti'
4
+ require 'optparse'
5
+
6
+ begin
7
+ option_parser = OptionParser.new do |opts|
8
+ executable_name = File.basename($PROGRAM_NAME)
9
+ opts.banner = "
10
+ Paletti takes an image and finds its background color as well as the best primary, secondary, and detail text colors that are readable on the background color.
11
+
12
+ Usage: #{executable_name} image_path
13
+ "
14
+ end
15
+ end
16
+
17
+ if ARGV.empty?
18
+ puts "Error: you must specifiy a path (local or URL) to an image."
19
+ puts option_parser.help
20
+ else
21
+ img = Paletti.new(ARGV[0])
22
+ bg_pixel = img.background_pixel
23
+ text_pixels = img.text_pixels
24
+ puts "Background color:\nR: #{bg_pixel.to_norm_rgba[0].to_i} G: #{bg_pixel.to_norm_rgba[1].to_i} B: #{bg_pixel.to_norm_rgba[2].to_i}\n\n"
25
+ puts "Text colors:\n"
26
+ text_pixels.each do |pixel|
27
+ puts "R: #{pixel.to_norm_rgba[0].to_i} G: #{pixel.to_norm_rgba[1].to_i} B: #{pixel.to_norm_rgba[2].to_i}\n"
28
+ end
29
+ end
@@ -0,0 +1,71 @@
1
+ require 'rmagick'
2
+ require_relative 'paletti/pixel'
3
+
4
+ class Paletti
5
+
6
+ def initialize(path_to_image)
7
+ # Load the image at the given path
8
+ @image = Magick::Image.read(path_to_image)[0]
9
+ end
10
+
11
+ def background_pixel
12
+ if @background_pixel
13
+ return @background_pixel
14
+ end
15
+
16
+ # Make an array of all the edge/border pixels
17
+ border_pixels = []
18
+ @image.each_pixel do |pixel, col_idx, row_idx|
19
+ if col_idx == 0 || row_idx == 0 || col_idx == @image.columns - 1 || row_idx == @image.rows - 1
20
+ border_pixels.push(pixel)
21
+ end
22
+ end
23
+
24
+ # Make a hash of the edge/border pixel frequencies and sort by frequency
25
+ border_pixel_counts = Hash.new(0)
26
+ border_pixels.each { |border_pixel| border_pixel_counts[border_pixel] += 1 }
27
+ sorted_border_pixels = border_pixel_counts.sort_by { |pixel, count| -count }
28
+ sorted_border_pixels = sorted_border_pixels.flatten.select! do |pixel|
29
+ pixel.class == Magick::Pixel
30
+ end
31
+
32
+ # Get a non black or white pixel if possible
33
+ pixel = sorted_border_pixels.first
34
+ backup_pixel = pixel.dup
35
+ while !pixel.nil? && pixel.is_black_or_white? && sorted_border_pixels.length > 1
36
+ sorted_border_pixels.delete(pixel)
37
+ pixel = sorted_border_pixels.find { |p| border_pixel_counts[p].to_f / border_pixel_counts[pixel].to_f > 0.05 && !p.is_black_or_white? }
38
+ end
39
+ return @background_pixel = pixel || backup_pixel
40
+ end
41
+
42
+ def text_pixels
43
+ if @text_pixels
44
+ return @text_pixels
45
+ end
46
+
47
+ # Make an array of all the pixels and sort by frequency
48
+ pixels = []
49
+ @image.each_pixel { |pixel| pixels.push(pixel) }
50
+ # For speed, just use a random sample of 250,000 pixels max
51
+ pixels = pixels.sample(250_000) if pixels.length > 250_000
52
+ pixel_counts = Hash.new(0)
53
+ pixels.each do |pixel|
54
+ if pixel.to_hsla[1] < 0.15 * 255.to_f
55
+ pixel = Magick::Pixel.from_hsla(pixel.to_hsla[0], 0.15 * 255.to_f, pixel.to_hsla[2], pixel.to_hsla[3])
56
+ end
57
+ pixel_counts[pixel] += 1
58
+ end
59
+ sorted_pixels = pixel_counts.sort_by { |pixel, count| -count }
60
+ sorted_pixels = sorted_pixels.flatten.select! { |pixel| pixel.class == Magick::Pixel }
61
+
62
+ # Get the most common three colors that are distinct from each other and the background color
63
+ @text_pixels = []
64
+ while @text_pixels.length < 3
65
+ found = (sorted_pixels.find { |pixel| pixel.is_contrasting?(self.background_pixel) && @text_pixels.all? { |text_pixel| text_pixel.is_distinct?(pixel) } })
66
+ @text_pixels.push(found)
67
+ end
68
+ return @text_pixels
69
+ end
70
+
71
+ end
@@ -0,0 +1,91 @@
1
+ class Magick::Pixel
2
+
3
+ def to_norm_rgba
4
+ [self.red.to_f * NORM_FACTOR, self.green.to_f * NORM_FACTOR, self.blue.to_f * NORM_FACTOR, self.opacity.to_f * NORM_FACTOR]
5
+ end
6
+
7
+ def norm_red
8
+ self.red.to_f * NORM_FACTOR
9
+ end
10
+
11
+ def norm_green
12
+ self.green.to_f * NORM_FACTOR
13
+ end
14
+
15
+ def norm_blue
16
+ self.blue.to_f * NORM_FACTOR
17
+ end
18
+
19
+ def norm_opacity
20
+ self.opacity.to_f * NORM_FACTOR
21
+ end
22
+
23
+ def is_black_or_white?
24
+ r, g, b = self.norm_red, self.norm_green, self.norm_blue
25
+ black_thresh = 0.09 * 255.to_f
26
+ white_thresh = 0.91 * 255.to_f
27
+ (r > white_thresh && g > white_thresh && b > white_thresh) || (r < black_thresh && g < black_thresh && b < black_thresh) ? true : false
28
+ end
29
+
30
+ def is_distinct?(other_pixel)
31
+ r, g, b, a = self.norm_red, self.norm_green, self.norm_blue, self.norm_opacity
32
+ other_r, other_g, other_b, other_a = other_pixel.norm_red, other_pixel.norm_green, other_pixel.norm_blue, other_pixel.norm_opacity
33
+ upper_thresh = 0.25 * 255.to_f
34
+ lower_thresh = 0.03 * 255.to_f
35
+
36
+ if (r - other_r).abs > upper_thresh || (g - other_g).abs > upper_thresh || (b - other_b).abs > upper_thresh || (a - other_a).abs > upper_thresh
37
+ if (r - g).abs < lower_thresh && (r - b).abs < lower_thresh && (other_r - other_g).abs < lower_thresh && (other_r - other_b).abs < lower_thresh
38
+ return false
39
+ end
40
+ return true
41
+ end
42
+ return false
43
+ end
44
+
45
+ def is_contrasting?(other_pixel)
46
+ # Calculate contrast using the W3C formula
47
+ # http://www.w3.org/TR/WCAG20-TECHS/G145.html
48
+ lum = self.luminance
49
+ other_pixel_lum = other_pixel.luminance
50
+ contrast = 0
51
+ if lum > other_pixel_lum
52
+ contrast = (lum + 0.05) / (other_pixel_lum + 0.05)
53
+ else
54
+ contrast = (other_pixel_lum + 0.05) / (lum + 0.05)
55
+ end
56
+
57
+ return contrast.to_f > 3
58
+ end
59
+
60
+ def is_dark?
61
+ return self.luminance < 0.5
62
+ end
63
+
64
+ def luminance
65
+ r, g, b = self.norm_red / 255.to_f, self.norm_green / 255.to_f, self.norm_blue / 255.to_f
66
+ if r <= 0.03928
67
+ r = r / 12.92
68
+ else
69
+ r = ((r + 0.055) / 1.055) ** 2.4
70
+ end
71
+
72
+ if g <= 0.03928
73
+ g = g / 12.92
74
+ else
75
+ g = ((g + 0.055) / 1.055) ** 2.4
76
+ end
77
+
78
+ if b <= 0.03928
79
+ b = b / 12.92
80
+ else
81
+ b = ((b + 0.055) / 1.055) ** 2.4
82
+ end
83
+
84
+ return 0.2126 * r + 0.7152 * g + 0.0722 * b
85
+ end
86
+
87
+ private
88
+
89
+ NORM_FACTOR = 255.to_f / Magick::QuantumRange.to_f
90
+
91
+ end
metadata ADDED
@@ -0,0 +1,49 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: Paletti
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Satyam Ghodasara
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2015-11-24 00:00:00.000000000 Z
12
+ dependencies: []
13
+ description: Paletti takes an image and finds its background color as well as the
14
+ best primary, secondary, and detail text colors that are readable on the background
15
+ color.
16
+ email: sghodas@gmail.com
17
+ executables:
18
+ - pal
19
+ extensions: []
20
+ extra_rdoc_files: []
21
+ files:
22
+ - bin/pal
23
+ - lib/paletti.rb
24
+ - lib/paletti/pixel.rb
25
+ homepage: https://github.com/sghodas/paletti
26
+ licenses:
27
+ - MIT
28
+ metadata: {}
29
+ post_install_message:
30
+ rdoc_options: []
31
+ require_paths:
32
+ - lib
33
+ required_ruby_version: !ruby/object:Gem::Requirement
34
+ requirements:
35
+ - - ">="
36
+ - !ruby/object:Gem::Version
37
+ version: '0'
38
+ required_rubygems_version: !ruby/object:Gem::Requirement
39
+ requirements:
40
+ - - ">="
41
+ - !ruby/object:Gem::Version
42
+ version: '0'
43
+ requirements: []
44
+ rubyforge_project:
45
+ rubygems_version: 2.4.5.1
46
+ signing_key:
47
+ specification_version: 4
48
+ summary: Paletti generates readable text colors to display over images.
49
+ test_files: []