miro 0.2.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.
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