miro 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,18 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
18
+ .rvmrc
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --color
2
+ --format progress
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in miro.gemspec
4
+ gemspec
data/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2012 Jon Buda
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,74 @@
1
+ # Miro
2
+
3
+ Extract the dominant colors from an image.
4
+
5
+ ## Feedback
6
+
7
+ Questions, Comments, Concerns, or Bugs?
8
+
9
+ [GitHub Issues For All!](https://github.com/jonbuda/miro/issues)
10
+
11
+ ## Installation
12
+
13
+ Add this line to your application's Gemfile:
14
+
15
+ gem 'miro'
16
+
17
+ And then execute:
18
+
19
+ $ bundle
20
+
21
+ Or install it yourself as:
22
+
23
+ $ gem install miro
24
+
25
+ ## Usage
26
+
27
+ Configuration
28
+
29
+ ```ruby
30
+ # Defaults
31
+ Miro.options # => {:image_magick_path => "/usr/bin/convert", :resolution => "150x150", :color_count => 8}
32
+ ```
33
+
34
+ ```ruby
35
+ # Setting
36
+ Miro.options[:image_magick_path] = '/usr/local/bin/convert'
37
+ Miro.options[:resolution] = '100x100'
38
+ Miro.options[:color_count] = 4
39
+ ```
40
+
41
+ Initializing an image
42
+
43
+ ```ruby
44
+ # Initialize with a local image
45
+ colors = Miro::DominantColors.new('/path/to/local/image.jpg')
46
+
47
+ # or Initialize with a remote image
48
+ colors = Miro::DominantColors.new('http://domain.com/path/to/image.jpg')
49
+ ```
50
+
51
+ Retrieving colors from an image
52
+
53
+ ```ruby
54
+ # Hex
55
+ colors.to_hex # => ["#51332a", "#2c1d18", "#6c4937", "#65514a", "#95644f", "#e0e7dc", "#a34d3a", "#9fa16b"]
56
+
57
+ # RGB
58
+ colors.to_rgb # => [[81, 51, 42], [44, 29, 24], [108, 73, 55], [101, 81, 74], [149, 100, 79], [224, 231, 220], [163, 77, 58], [159, 161, 107]]
59
+
60
+ # RGB with Alpha channels
61
+ colors.to_rgba # => [[82, 37, 40, 255], [48, 17, 19, 255], [109, 70, 71, 255], [221, 158, 48, 255], [168, 103, 48, 255], [226, 178, 79, 255], [191, 146, 65, 255], [199, 165, 150, 255]]
62
+ ```
63
+
64
+ ## Contributing
65
+
66
+ 1. Fork it
67
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
68
+ 3. Commit your changes (`git commit -am 'Added some feature'`)
69
+ 4. Push to the branch (`git push origin my-new-feature`)
70
+ 5. Create new Pull Request
71
+
72
+ ## License
73
+
74
+ Copyright 2012 Jon Buda. This is free software, and may be redistributed under the terms specified in the LICENSE file.
data/Rakefile ADDED
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env rake
2
+ require "bundler/gem_tasks"
@@ -0,0 +1,85 @@
1
+ module Miro
2
+ class DominantColors
3
+ attr_accessor :src_image_path
4
+
5
+ def initialize(src_image_path)
6
+ @src_image_path = src_image_path
7
+ end
8
+
9
+ def to_hex
10
+ sorted_pixels.collect {|c| ChunkyPNG::Color.to_hex c, false }
11
+ end
12
+
13
+ def to_rgb
14
+ sorted_pixels.collect {|c| ChunkyPNG::Color.to_truecolor_bytes c }
15
+ end
16
+
17
+ def to_rgba
18
+ sorted_pixels.collect {|c| ChunkyPNG::Color.to_truecolor_alpha_bytes c }
19
+ end
20
+
21
+ def sorted_pixels
22
+ @sorted_pixels ||= extract_colors_from_image
23
+ end
24
+
25
+ private
26
+ def extract_colors_from_image
27
+ downsample_colors_and_convert_to_png!
28
+ colors = sort_by_dominant_color
29
+ cleanup_temporary_files!
30
+ return colors
31
+ end
32
+
33
+ def remote_source_image?
34
+ @src_image_path =~ /^https?:\/\//
35
+ end
36
+
37
+ def downsample_colors_and_convert_to_png!
38
+ @source_image = open_source_image
39
+ @downsampled_image = open_downsampled_image
40
+ command.run
41
+ end
42
+
43
+ def command
44
+ Cocaine::CommandLine.new(Miro.options[:image_magick_path], ":in -resize :resolution -colors :colors :out",
45
+ :in => File.expand_path(@source_image.path),
46
+ :resolution => Miro.options[:resolution],
47
+ :colors => Miro.options[:color_count].to_s,
48
+ :out => File.expand_path(@downsampled_image.path))
49
+ end
50
+
51
+ def open_source_image
52
+ if remote_source_image?
53
+ original_extension = URI.parse(@src_image_path).path.split('.').last
54
+
55
+ tempfile = Tempfile.open(["source", ".#{original_extension}"])
56
+ remote_file_data = open(@src_image_path).read
57
+
58
+ tempfile.write(RUBY_VERSION =~ /1.9/ ? remote_file_data.force_encoding("UTF-8") : remote_file_data)
59
+ return tempfile
60
+ else
61
+ return File.open(@src_image_path)
62
+ end
63
+ end
64
+
65
+ def open_downsampled_image
66
+ tempfile = Tempfile.open(["downsampled", '.png'])
67
+ tempfile.binmode
68
+ tempfile
69
+ end
70
+
71
+ def group_pixels_by_color
72
+ chunky_png = ChunkyPNG::Image.from_file File.expand_path(@downsampled_image.path)
73
+ chunky_png.pixels.group_by { |pixel| pixel }
74
+ end
75
+
76
+ def sort_by_dominant_color
77
+ group_pixels_by_color.sort_by { |k,v| v.size }.reverse.flatten.uniq
78
+ end
79
+
80
+ def cleanup_temporary_files!
81
+ @source_image.close(true) if remote_source_image?
82
+ @downsampled_image.close(true)
83
+ end
84
+ end
85
+ end
@@ -0,0 +1,3 @@
1
+ module Miro
2
+ VERSION = "0.2.0"
3
+ end
data/lib/miro.rb ADDED
@@ -0,0 +1,19 @@
1
+ require "miro/version"
2
+ require "oily_png"
3
+ require "cocaine"
4
+ require "tempfile"
5
+ require "open-uri"
6
+
7
+ require "miro/dominant_colors"
8
+
9
+ module Miro
10
+ class << self
11
+ def options
12
+ @options ||= {
13
+ :image_magick_path => "/usr/bin/convert",
14
+ :resolution => "150x150",
15
+ :color_count => 8
16
+ }
17
+ end
18
+ end
19
+ end
data/miro.gemspec ADDED
@@ -0,0 +1,25 @@
1
+ # -*- encoding: utf-8 -*-
2
+ require File.expand_path('../lib/miro/version', __FILE__)
3
+
4
+ Gem::Specification.new do |gem|
5
+ gem.authors = ["Jon Buda"]
6
+ gem.email = ["jon.buda@gmail.com"]
7
+ gem.description = %q{Extract the dominant colors from an image.}
8
+ gem.summary = %q{Extract the dominant colors from an image.}
9
+ gem.homepage = "https://github.com/jonbuda/miro"
10
+
11
+ gem.requirements = 'ImageMagick'
12
+
13
+ gem.add_dependency "cocaine"
14
+ gem.add_dependency "oily_png"
15
+
16
+ gem.add_development_dependency "rspec"
17
+ gem.add_development_dependency "fakeweb"
18
+
19
+ gem.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
20
+ gem.files = `git ls-files`.split("\n")
21
+ gem.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
22
+ gem.name = "miro"
23
+ gem.require_paths = ["lib"]
24
+ gem.version = Miro::VERSION
25
+ end
@@ -0,0 +1,134 @@
1
+ require 'spec_helper'
2
+
3
+ describe Miro::DominantColors do
4
+ let(:subject) { Miro::DominantColors.new('/path/to/file') }
5
+ let(:mock_source_image) { double('file', :path => '/path/to/source_image').as_null_object }
6
+ let(:mock_downsampled_image) { double('file', :path => '/path/to/downsampled_image').as_null_object }
7
+ let(:pixel_data) {
8
+ [ 2678156287,
9
+ 1362307839, 1362307839, 1362307839, 1362307839, 1362307839, 1362307839, 1362307839, 1362307839,
10
+ 2506379263, 2506379263, 2506379263, 2506379263,
11
+ 2739747583, 2739747583,
12
+ 1816737791, 1816737791, 1816737791, 1816737791, 1816737791, 1816737791,
13
+ 1699826431, 1699826431, 1699826431, 1699826431, 1699826431,
14
+ 3773291775, 3773291775, 3773291775,
15
+ 740104447, 740104447, 740104447, 740104447, 740104447, 740104447, 740104447 ] }
16
+ let(:chunky_png_results) { double('chunkypng', :pixels => pixel_data) }
17
+ let(:expected_pixels) { [1362307839, 740104447, 1816737791, 1699826431, 2506379263, 3773291775, 2739747583, 2678156287] }
18
+ let(:expected_hex_values) { ["#51332a", "#2c1d18", "#6c4937", "#65514a", "#95644f", "#e0e7dc", "#a34d3a", "#9fa16b"] }
19
+ let(:expected_rgba_values) { [[81, 51, 42, 255], [44, 29, 24, 255], [108, 73, 55, 255], [101, 81, 74, 255], [149, 100, 79, 255], [224, 231, 220, 255], [163, 77, 58, 255], [159, 161, 107, 255]] }
20
+ let(:expected_rgb_values) { [[81, 51, 42], [44, 29, 24], [108, 73, 55], [101, 81, 74], [149, 100, 79], [224, 231, 220], [163, 77, 58], [159, 161, 107]] }
21
+
22
+ before do
23
+ Cocaine::CommandLine.stub(:new).and_return(double('command', :run => true))
24
+ ChunkyPNG::Image.stub(:from_file).and_return(chunky_png_results)
25
+ end
26
+
27
+ it "takes a paramter and sets the source image path" do
28
+ miro = Miro::DominantColors.new('path/to/file')
29
+ miro.src_image_path.should eq('path/to/file')
30
+ end
31
+
32
+ describe "#sorted_pixels" do
33
+ before { subject.stub(:extract_colors_from_image).and_return(expected_pixels) }
34
+
35
+ it "extracts colors from the image the first time it's called" do
36
+ subject.should_receive(:extract_colors_from_image).and_return(expected_pixels)
37
+ subject.sorted_pixels
38
+ end
39
+
40
+ it "returns previously extracted colors on subsequent calls" do
41
+ subject.should_receive(:extract_colors_from_image).once
42
+ subject.sorted_pixels
43
+ subject.sorted_pixels
44
+ end
45
+ end
46
+
47
+ context "when extracting colors" do
48
+ before do
49
+ File.stub(:open).and_return(mock_source_image)
50
+ end
51
+
52
+ it "opens the tempfile for the downsampled image" do
53
+ Tempfile.should_receive(:open).with(['downsampled', '.png']).and_return(mock_downsampled_image)
54
+ subject.sorted_pixels
55
+ end
56
+
57
+ it "runs the imagemagick command line with the correct arguments" do
58
+ subject.stub(:open_downsampled_image).and_return(mock_downsampled_image)
59
+ Cocaine::CommandLine.should_receive(:new).with(Miro.options[:image_magick_path],
60
+ ":in -resize :resolution -colors :colors :out",
61
+ :in => '/path/to/source_image',
62
+ :resolution => Miro.options[:resolution],
63
+ :colors => Miro.options[:color_count].to_s,
64
+ :out => '/path/to/downsampled_image')
65
+ subject.sorted_pixels
66
+ end
67
+
68
+ it "sorts the colors from most dominant to least" do
69
+ subject.stub(:open_downsampled_image).and_return(mock_downsampled_image)
70
+ subject.sorted_pixels.should eq(expected_pixels)
71
+ end
72
+ end
73
+
74
+ context "when the source image is a local image file" do
75
+ let(:subject) { Miro::DominantColors.new('/path/to/image.jpg') }
76
+
77
+ before do
78
+ File.stub(:open).and_return(mock_source_image)
79
+ subject.stub(:open_downsampled_image).and_return(mock_downsampled_image)
80
+ end
81
+
82
+ it "opens for the file resource" do
83
+ File.should_receive(:open).with(subject.src_image_path)
84
+ subject.sorted_pixels
85
+ end
86
+
87
+ it "deletes only temporary downsampled file when finished" do
88
+ mock_downsampled_image.should_receive(:close).with(true)
89
+ mock_source_image.should_not_receive(:close)
90
+ subject.sorted_pixels
91
+ end
92
+ end
93
+
94
+ context "when the source image is a remote image" do
95
+ let(:subject) { Miro::DominantColors.new('http://domain.com/to/image.jpg') }
96
+
97
+ before do
98
+ subject.stub(:open_downsampled_image).and_return(mock_downsampled_image)
99
+ Tempfile.stub(:open).and_return(mock_source_image)
100
+ end
101
+
102
+ it "opens for the file resource" do
103
+ Tempfile.should_receive(:open).with(['source','.jpg']).and_return(mock_source_image)
104
+ subject.sorted_pixels
105
+ end
106
+
107
+ it "deletes the temporary source file when finished" do
108
+ mock_source_image.should_receive(:close).with(true)
109
+ subject.sorted_pixels
110
+ end
111
+ end
112
+
113
+ context "when retrieving various color values" do
114
+ before { subject.stub(:sorted_pixels).and_return(expected_pixels) }
115
+
116
+ describe "#to_hex" do
117
+ it "converts sorted pixel data into hex color values" do
118
+ subject.to_hex.should eq(expected_hex_values)
119
+ end
120
+ end
121
+
122
+ describe "#to_rgb" do
123
+ it "converts sorted pixel data into rgb color values" do
124
+ subject.to_rgb.should eq(expected_rgb_values)
125
+ end
126
+ end
127
+
128
+ describe "#to_rgba" do
129
+ it "converts sorted pixel data into rgba color values" do
130
+ subject.to_rgba.should eq(expected_rgba_values)
131
+ end
132
+ end
133
+ end
134
+ end
data/spec/miro_spec.rb ADDED
@@ -0,0 +1,16 @@
1
+ require 'spec_helper'
2
+
3
+ describe Miro do
4
+ describe '.options' do
5
+ it "has default options" do
6
+ Miro.options[:image_magick_path].should eq('/usr/bin/convert')
7
+ Miro.options[:color_count].should eq(8)
8
+ Miro.options[:resolution].should eq('150x150')
9
+ end
10
+
11
+ it "can override the default options" do
12
+ Miro.options[:image_magick_path] = '/path/to/command'
13
+ Miro.options[:image_magick_path].should == '/path/to/command'
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,17 @@
1
+ # This file was generated by the `rspec --init` command. Conventionally, all
2
+ # specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`.
3
+ # Require this file using `require "spec_helper.rb"` to ensure that it is only
4
+ # loaded once.
5
+ #
6
+ # See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
7
+ #
8
+ require 'miro'
9
+ require 'fakeweb'
10
+
11
+ RSpec.configure do |config|
12
+ config.treat_symbols_as_metadata_keys_with_true_values = true
13
+ config.run_all_when_everything_filtered = true
14
+ config.filter_run :focus
15
+ end
16
+
17
+ FakeWeb.register_uri(:get, "http://domain.com/to/image.jpg", :body => "image data")
metadata ADDED
@@ -0,0 +1,137 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: miro
3
+ version: !ruby/object:Gem::Version
4
+ hash: 23
5
+ prerelease:
6
+ segments:
7
+ - 0
8
+ - 2
9
+ - 0
10
+ version: 0.2.0
11
+ platform: ruby
12
+ authors:
13
+ - Jon Buda
14
+ autorequire:
15
+ bindir: bin
16
+ cert_chain: []
17
+
18
+ date: 2012-01-25 00:00:00 -06:00
19
+ default_executable:
20
+ dependencies:
21
+ - !ruby/object:Gem::Dependency
22
+ name: cocaine
23
+ prerelease: false
24
+ requirement: &id001 !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ">="
28
+ - !ruby/object:Gem::Version
29
+ hash: 3
30
+ segments:
31
+ - 0
32
+ version: "0"
33
+ type: :runtime
34
+ version_requirements: *id001
35
+ - !ruby/object:Gem::Dependency
36
+ name: oily_png
37
+ prerelease: false
38
+ requirement: &id002 !ruby/object:Gem::Requirement
39
+ none: false
40
+ requirements:
41
+ - - ">="
42
+ - !ruby/object:Gem::Version
43
+ hash: 3
44
+ segments:
45
+ - 0
46
+ version: "0"
47
+ type: :runtime
48
+ version_requirements: *id002
49
+ - !ruby/object:Gem::Dependency
50
+ name: rspec
51
+ prerelease: false
52
+ requirement: &id003 !ruby/object:Gem::Requirement
53
+ none: false
54
+ requirements:
55
+ - - ">="
56
+ - !ruby/object:Gem::Version
57
+ hash: 3
58
+ segments:
59
+ - 0
60
+ version: "0"
61
+ type: :development
62
+ version_requirements: *id003
63
+ - !ruby/object:Gem::Dependency
64
+ name: fakeweb
65
+ prerelease: false
66
+ requirement: &id004 !ruby/object:Gem::Requirement
67
+ none: false
68
+ requirements:
69
+ - - ">="
70
+ - !ruby/object:Gem::Version
71
+ hash: 3
72
+ segments:
73
+ - 0
74
+ version: "0"
75
+ type: :development
76
+ version_requirements: *id004
77
+ description: Extract the dominant colors from an image.
78
+ email:
79
+ - jon.buda@gmail.com
80
+ executables: []
81
+
82
+ extensions: []
83
+
84
+ extra_rdoc_files: []
85
+
86
+ files:
87
+ - .gitignore
88
+ - .rspec
89
+ - Gemfile
90
+ - LICENSE
91
+ - README.md
92
+ - Rakefile
93
+ - lib/miro.rb
94
+ - lib/miro/dominant_colors.rb
95
+ - lib/miro/version.rb
96
+ - miro.gemspec
97
+ - spec/miro/dominant_colors_spec.rb
98
+ - spec/miro_spec.rb
99
+ - spec/spec_helper.rb
100
+ has_rdoc: true
101
+ homepage: https://github.com/jonbuda/miro
102
+ licenses: []
103
+
104
+ post_install_message:
105
+ rdoc_options: []
106
+
107
+ require_paths:
108
+ - lib
109
+ required_ruby_version: !ruby/object:Gem::Requirement
110
+ none: false
111
+ requirements:
112
+ - - ">="
113
+ - !ruby/object:Gem::Version
114
+ hash: 3
115
+ segments:
116
+ - 0
117
+ version: "0"
118
+ required_rubygems_version: !ruby/object:Gem::Requirement
119
+ none: false
120
+ requirements:
121
+ - - ">="
122
+ - !ruby/object:Gem::Version
123
+ hash: 3
124
+ segments:
125
+ - 0
126
+ version: "0"
127
+ requirements:
128
+ - ImageMagick
129
+ rubyforge_project:
130
+ rubygems_version: 1.5.3
131
+ signing_key:
132
+ specification_version: 3
133
+ summary: Extract the dominant colors from an image.
134
+ test_files:
135
+ - spec/miro/dominant_colors_spec.rb
136
+ - spec/miro_spec.rb
137
+ - spec/spec_helper.rb