├── .rspec ├── .coveralls.yml ├── spec ├── assets │ └── image.png ├── spec_helper.rb ├── rails_dominant_colors_base64_spec.rb ├── rails_dominant_colors_path_spec.rb └── rails_dominant_colors_url_spec.rb ├── lib ├── rails_dominant_colors │ ├── version.rb │ ├── path.rb │ ├── base64.rb │ ├── url.rb │ └── base.rb └── rails_dominant_colors.rb ├── Gemfile ├── bin ├── setup └── console ├── Rakefile ├── .travis.yml ├── .gitignore ├── .rubocop.yml ├── LICENSE.txt ├── Gemfile.lock ├── rails_dominant_colors.gemspec └── README.md /.rspec: -------------------------------------------------------------------------------- 1 | --format documentation 2 | --color 3 | --require spec_helper -------------------------------------------------------------------------------- /.coveralls.yml: -------------------------------------------------------------------------------- 1 | service_name: travis-pro 2 | repo_token: ZNtqKdszxNXImMHT3tBJ3jI0UY8k7kJ6y -------------------------------------------------------------------------------- /spec/assets/image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenGems/rails_dominant_colors/HEAD/spec/assets/image.png -------------------------------------------------------------------------------- /lib/rails_dominant_colors/version.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module RailsDominantColors 4 | VERSION = '0.2.1' 5 | end 6 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | source 'https://rubygems.org' 4 | 5 | # Specify your gem's dependencies in rails_dominant_colors.gemspec 6 | gemspec 7 | -------------------------------------------------------------------------------- /bin/setup: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -euo pipefail 3 | IFS=$'\n\t' 4 | set -vx 5 | 6 | bundle install 7 | 8 | # Do any other automated setup that you need to do here 9 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'bundler/gem_tasks' 4 | require 'rspec/core/rake_task' 5 | 6 | RSpec::Core::RakeTask.new(:spec) 7 | 8 | task default: :spec 9 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | --- 2 | os: linux 3 | language: ruby 4 | cache: bundler 5 | rvm: 6 | - 2.5.5 7 | - 2.6.3 8 | - 2.7.0 9 | before_install: gem install bundler 10 | before_script: 11 | - bundle install 12 | script: RAILS_ENV=test bundle exec rspec -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /.bundle/ 2 | /.yardoc 3 | /_yardoc/ 4 | /coverage/ 5 | /doc/ 6 | /pkg/ 7 | /spec/reports/ 8 | /tmp/ 9 | 10 | # rspec failure tracking 11 | .rspec_status 12 | 13 | # IDE 14 | /.idea 15 | .idea/workspace.xml 16 | .rakeTasks 17 | .generators 18 | 19 | # Gem 20 | *.gem 21 | 22 | # Logs 23 | spec/dummy/log/*.log 24 | spec/dummy/tmp/ -------------------------------------------------------------------------------- /bin/console: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # frozen_string_literal: true 3 | 4 | require 'bundler/setup' 5 | require 'rails_dominant_colors' 6 | 7 | # You can add fixtures and/or initialization code here to make experimenting 8 | # with your gem easier. You can also use a different console, if you like. 9 | 10 | # (If you use this, don't forget to add pry to your Gemfile!) 11 | # require "pry" 12 | # Pry.start 13 | 14 | require 'irb' 15 | IRB.start(__FILE__) 16 | -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'bundler/setup' 4 | require 'simplecov' 5 | require 'coveralls' 6 | 7 | SimpleCov.start 8 | Coveralls.wear! 9 | 10 | require 'rails_dominant_colors' 11 | 12 | RSpec.configure do |config| 13 | # Enable flags like --only-failures and --next-failure 14 | config.example_status_persistence_file_path = '.rspec_status' 15 | 16 | # Disable RSpec exposing methods globally on `Module` and `main` 17 | config.disable_monkey_patching! 18 | 19 | config.expect_with :rspec do |c| 20 | c.syntax = :expect 21 | end 22 | end 23 | -------------------------------------------------------------------------------- /.rubocop.yml: -------------------------------------------------------------------------------- 1 | Documentation: 2 | Enabled: false 3 | Metrics/LineLength: 4 | IgnoredPatterns: ['(\A|\s)#'] 5 | Max: 120 6 | Layout/EmptyLineAfterGuardClause: 7 | Enabled: false 8 | Layout/EmptyLinesAroundBlockBody: 9 | Enabled: false 10 | Metrics/MethodLength: 11 | Max: 25 12 | Metrics/BlockLength: 13 | Max: 30 14 | Metrics/AbcSize: 15 | Max: 25 16 | Metrics/CyclomaticComplexity: 17 | Max: 25 18 | Style/RegexpLiteral: 19 | EnforcedStyle: percent_r 20 | Naming/MethodParameterName: 21 | AllowedNames: 22 | - 'r' 23 | - 'g' 24 | - 'b' 25 | - 'a' 26 | - 'h' 27 | - 's' 28 | - 'l' -------------------------------------------------------------------------------- /lib/rails_dominant_colors.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'rails_dominant_colors/base' 4 | require 'rails_dominant_colors/base64' 5 | require 'rails_dominant_colors/path' 6 | require 'rails_dominant_colors/url' 7 | 8 | module RailsDominantColors 9 | class << self 10 | def base64(source, colors = 5) 11 | RailsDominantColors::Base64.new(source, colors) 12 | end 13 | 14 | def url(source, colors = 5) 15 | RailsDominantColors::Url.new(source, colors) 16 | end 17 | 18 | def path(source, colors = 5) 19 | RailsDominantColors::Path.new(source, colors) 20 | end 21 | end 22 | end 23 | -------------------------------------------------------------------------------- /lib/rails_dominant_colors/path.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module RailsDominantColors 4 | class Path < Base 5 | def initialize(source, colors) 6 | raise(EmptySource) if source.nil? || source.empty? 7 | raise(FileNotFound) unless file_exist?(source) 8 | raise(NotAnImage) unless file_is_image?(source) 9 | 10 | super(source, colors) 11 | end 12 | 13 | private 14 | 15 | def file_exist?(source) 16 | File.exist?(source) 17 | end 18 | 19 | def file_is_image?(source) 20 | extension = File.extname(source) 21 | extensions.values.include?(extension) 22 | end 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /lib/rails_dominant_colors/base64.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'base64' 4 | 5 | module RailsDominantColors 6 | class Base64 < Base 7 | def initialize(source, colors) 8 | raise(EmptySource) if source.nil? || source.empty? 9 | 10 | super(process(source), colors) 11 | end 12 | 13 | private 14 | 15 | def process(source) 16 | res = source.match(%r{data:(?[-\w]+/[-\w+.]+)?;base64,(?.*)}) 17 | raise(InvalidBase64) if res.nil? 18 | 19 | content_type = res[:type].to_sym 20 | extension = extensions[content_type] 21 | raise(NotAnImage) if extension.nil? 22 | 23 | tempfile(extension, ::Base64.decode64(res[:content])) 24 | end 25 | 26 | def tempfile(extension, body) 27 | tempfile = Tempfile.open(['source', ".#{extension}"]) 28 | tempfile.write(body) 29 | tempfile.close 30 | tempfile.path 31 | end 32 | end 33 | end 34 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2020 OpenGems 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. -------------------------------------------------------------------------------- /Gemfile.lock: -------------------------------------------------------------------------------- 1 | PATH 2 | remote: . 3 | specs: 4 | rails_dominant_colors (0.2.0) 5 | mini_magick (~> 4.10) 6 | 7 | GEM 8 | remote: https://rubygems.org/ 9 | specs: 10 | coveralls (0.8.23) 11 | json (>= 1.8, < 3) 12 | simplecov (~> 0.16.1) 13 | term-ansicolor (~> 1.3) 14 | thor (>= 0.19.4, < 2.0) 15 | tins (~> 1.6) 16 | diff-lcs (1.4.4) 17 | docile (1.3.2) 18 | json (2.3.1) 19 | mini_magick (4.10.1) 20 | rake (13.0.1) 21 | rspec (3.9.0) 22 | rspec-core (~> 3.9.0) 23 | rspec-expectations (~> 3.9.0) 24 | rspec-mocks (~> 3.9.0) 25 | rspec-core (3.9.2) 26 | rspec-support (~> 3.9.3) 27 | rspec-expectations (3.9.2) 28 | diff-lcs (>= 1.2.0, < 2.0) 29 | rspec-support (~> 3.9.0) 30 | rspec-mocks (3.9.1) 31 | diff-lcs (>= 1.2.0, < 2.0) 32 | rspec-support (~> 3.9.0) 33 | rspec-support (3.9.3) 34 | simplecov (0.16.1) 35 | docile (~> 1.1) 36 | json (>= 1.8, < 3) 37 | simplecov-html (~> 0.10.0) 38 | simplecov-html (0.10.2) 39 | sync (0.5.0) 40 | term-ansicolor (1.7.1) 41 | tins (~> 1.0) 42 | thor (1.0.1) 43 | tins (1.25.0) 44 | sync 45 | 46 | PLATFORMS 47 | ruby 48 | 49 | DEPENDENCIES 50 | bundler (~> 2.0) 51 | coveralls (~> 0.8) 52 | rails_dominant_colors! 53 | rake (~> 13.0) 54 | rspec (~> 3.9) 55 | simplecov (~> 0.16) 56 | 57 | BUNDLED WITH 58 | 2.1.4 59 | -------------------------------------------------------------------------------- /lib/rails_dominant_colors/url.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'net/http' 4 | 5 | module RailsDominantColors 6 | class Url < Base 7 | def initialize(source, colors) 8 | raise(EmptySource) if source.nil? || source.empty? 9 | 10 | super(process(source), colors) 11 | end 12 | 13 | def process(source) 14 | raise(InvalidUrl) unless valid_url?(source) 15 | 16 | ri = remote_image(source) 17 | content_type = ri.content_type.to_sym 18 | extension = extensions[content_type] 19 | 20 | raise(NotAnImage) if extension.nil? 21 | 22 | tempfile(extension, ri.body) 23 | end 24 | 25 | private 26 | 27 | def remote_image(url) 28 | uri = uri(url) 29 | req = Net::HTTP::Get.new(uri.to_s) 30 | ssl = uri.scheme == 'https' 31 | Net::HTTP.start(uri.host, uri.port, use_ssl: ssl) do |http| 32 | http.request(req) 33 | end 34 | rescue StandardError 35 | raise(UrlNotFound) 36 | end 37 | 38 | def valid_url?(url) 39 | url =~ URI::DEFAULT_PARSER.make_regexp(%w[http https]) 40 | end 41 | 42 | def uri(url) 43 | URI.parse(url) 44 | rescue StandardError 45 | raise(InvalidUrl) 46 | end 47 | 48 | def tempfile(extension, body) 49 | tempfile = Tempfile.open(['source', extension]) 50 | tempfile.write(body) 51 | tempfile.close 52 | tempfile.path 53 | end 54 | end 55 | end 56 | -------------------------------------------------------------------------------- /rails_dominant_colors.gemspec: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require_relative 'lib/rails_dominant_colors/version' 4 | 5 | Gem::Specification.new do |spec| 6 | spec.name = 'rails_dominant_colors' 7 | spec.version = RailsDominantColors::VERSION 8 | spec.authors = ['Boris BRESCIANI'] 9 | spec.email = ['boris2bresciani@gmail.com'] 10 | 11 | spec.summary = 'Extract the dominant color(s) from an image' 12 | spec.description = <<~TEXT 13 | Extract the dominant color(s) from an image (remote image, locally image, base64 image etc...) 14 | Get HEX(A) color(s), RGB(A) color(s), HSL(A) color(s) 15 | TEXT 16 | 17 | spec.homepage = 'https://github.com/OpenGems/rails_dominant_colors' 18 | spec.license = 'MIT' 19 | 20 | spec.metadata['homepage_uri'] = spec.homepage 21 | spec.metadata['source_code_uri'] = spec.homepage 22 | 23 | spec.files = Dir.chdir(File.expand_path(__dir__)) do 24 | `git ls-files -z`.split("\x0") 25 | .reject { |f| f.match(%r{^(test|spec|features)/}) } 26 | end 27 | spec.bindir = 'exe' 28 | spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) } 29 | spec.require_paths = ['lib'] 30 | 31 | spec.add_development_dependency 'bundler', '~> 2.5' 32 | spec.add_development_dependency 'base64', '~> 0.2' 33 | spec.add_development_dependency 'coveralls', '~> 0.8' 34 | spec.add_development_dependency 'rake', '~> 13.2' 35 | spec.add_development_dependency 'rspec', '~> 3.13' 36 | spec.add_development_dependency 'simplecov', '~> 0.16' 37 | 38 | spec.add_dependency 'mini_magick', '~> 5.0' 39 | end 40 | -------------------------------------------------------------------------------- /spec/rails_dominant_colors_base64_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | RSpec.describe RailsDominantColors::Base64 do 4 | let(:rdc_base) do 5 | file = File.open('spec/assets/image_base64.txt') 6 | RailsDominantColors.base64(file.read) 7 | end 8 | 9 | it 'Returns hex(a) values' do 10 | hexs = rdc_base.to_hex 11 | expect(hexs).to be_a_kind_of(Array) 12 | hex = hexs[0].gsub('#', '') 13 | expect(hex.to_i(16).to_s(16)).to eql(hex.downcase) 14 | 15 | hexas = rdc_base.to_hex_alpha 16 | expect(hexas).to be_a_kind_of(Array) 17 | hexa = hexas[0].gsub('#', '') 18 | expect(hexa.to_i(16).to_s(16)).to eql(hexa.downcase) 19 | end 20 | 21 | it 'Returns rgb(a) values' do 22 | rgbs = rdc_base.to_rgb 23 | expect(rgbs).to be_a_kind_of(Array) 24 | rgb = rgbs[0] 25 | expect(rgb.size).to eql(3) 26 | 27 | rgbas = rdc_base.to_rgb_alpha 28 | expect(rgbas).to be_a_kind_of(Array) 29 | rgba = rgbas[0] 30 | expect(rgba.size).to eql(4) 31 | end 32 | 33 | it 'Returns hsl(a) values' do 34 | hsls = rdc_base.to_hsl 35 | expect(hsls).to be_a_kind_of(Array) 36 | hsl = hsls[0] 37 | expect(hsl.size).to eql(3) 38 | 39 | hslas = rdc_base.to_hsl_alpha 40 | expect(hslas).to be_a_kind_of(Array) 41 | hsla = hslas[0] 42 | expect(hsla.size).to eql(4) 43 | end 44 | 45 | it 'Returns pct values' do 46 | pcts = rdc_base.to_pct 47 | expect(pcts.sum).to be >= 99.99 48 | end 49 | 50 | it 'Returns raises errors' do 51 | expect do 52 | RailsDominantColors.base64('') 53 | end.to raise_error(RailsDominantColors::Base::EmptySource) 54 | 55 | expect do 56 | RailsDominantColors.base64('test') 57 | end.to raise_error(RailsDominantColors::Base::InvalidBase64) 58 | end 59 | end 60 | -------------------------------------------------------------------------------- /spec/rails_dominant_colors_path_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | RSpec.describe RailsDominantColors::Path do 4 | let(:rdc_path) do 5 | RailsDominantColors.path('spec/assets/image.png') 6 | end 7 | 8 | it 'Returns hex(a) values' do 9 | hexs = rdc_path.to_hex 10 | expect(hexs).to be_a_kind_of(Array) 11 | hex = hexs[0].gsub('#', '') 12 | expect(hex.to_i(16).to_s(16)).to eql(hex.downcase) 13 | 14 | hexas = rdc_path.to_hex_alpha 15 | expect(hexas).to be_a_kind_of(Array) 16 | hexa = hexas[0].gsub('#', '') 17 | expect(hexa.to_i(16).to_s(16)).to eql(hexa.downcase) 18 | end 19 | 20 | it 'Returns rgb(a) values' do 21 | rgbs = rdc_path.to_rgb 22 | expect(rgbs).to be_a_kind_of(Array) 23 | rgb = rgbs[0] 24 | expect(rgb.size).to eql(3) 25 | 26 | rgbas = rdc_path.to_rgb_alpha 27 | expect(rgbas).to be_a_kind_of(Array) 28 | rgba = rgbas[0] 29 | expect(rgba.size).to eql(4) 30 | end 31 | 32 | it 'Returns hsl(a) values' do 33 | hsls = rdc_path.to_hsl 34 | expect(hsls).to be_a_kind_of(Array) 35 | hsl = hsls[0] 36 | expect(hsl.size).to eql(3) 37 | 38 | hslas = rdc_path.to_hsl_alpha 39 | expect(hslas).to be_a_kind_of(Array) 40 | hsla = hslas[0] 41 | expect(hsla.size).to eql(4) 42 | end 43 | 44 | it 'Returns pct values' do 45 | pcts = rdc_path.to_pct 46 | expect(pcts.sum).to be >= 99.99 47 | end 48 | 49 | it 'Returns raises errors' do 50 | expect do 51 | RailsDominantColors.path('') 52 | end.to raise_error(RailsDominantColors::Base::EmptySource) 53 | 54 | expect do 55 | RailsDominantColors.path('spec/assets/image.jpg') 56 | end.to raise_error(RailsDominantColors::Base::FileNotFound) 57 | 58 | expect do 59 | RailsDominantColors.path('spec/assets/image_base64.txt') 60 | end.to raise_error(RailsDominantColors::Base::NotAnImage) 61 | end 62 | end 63 | -------------------------------------------------------------------------------- /spec/rails_dominant_colors_url_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | RSpec.describe RailsDominantColors::Url do 4 | let(:rdc_url) do 5 | RailsDominantColors.url( 6 | 'https://cdn.pixabay.com/photo/2014/04/03/10/22/ruby-310233_1280.png' 7 | ) 8 | end 9 | 10 | it 'Returns hex(a) values' do 11 | hexs = rdc_url.to_hex 12 | expect(hexs).to be_a_kind_of(Array) 13 | hex = hexs[0].gsub('#', '') 14 | expect(hex.to_i(16).to_s(16)).to eql(hex.downcase) 15 | 16 | hexas = rdc_url.to_hex_alpha 17 | expect(hexas).to be_a_kind_of(Array) 18 | hexa = hexas[0].gsub('#', '') 19 | expect(hexa.to_i(16).to_s(16)).to eql(hexa.downcase) 20 | end 21 | 22 | it 'Returns rgb(a) values' do 23 | rgbs = rdc_url.to_rgb 24 | expect(rgbs).to be_a_kind_of(Array) 25 | rgb = rgbs[0] 26 | expect(rgb.size).to eql(3) 27 | 28 | rgbas = rdc_url.to_rgb_alpha 29 | expect(rgbas).to be_a_kind_of(Array) 30 | rgba = rgbas[0] 31 | expect(rgba.size).to eql(4) 32 | end 33 | 34 | it 'Returns hsl(a) values' do 35 | hsls = rdc_url.to_hsl 36 | expect(hsls).to be_a_kind_of(Array) 37 | hsl = hsls[0] 38 | expect(hsl.size).to eql(3) 39 | 40 | hslas = rdc_url.to_hsl_alpha 41 | expect(hslas).to be_a_kind_of(Array) 42 | hsla = hslas[0] 43 | expect(hsla.size).to eql(4) 44 | end 45 | 46 | it 'Returns pct values' do 47 | pcts = rdc_url.to_pct 48 | expect(pcts.sum).to be >= 99.99 49 | end 50 | 51 | it 'Returns raises errors' do 52 | expect do 53 | RailsDominantColors.url('') 54 | end.to raise_error(RailsDominantColors::Base::EmptySource) 55 | 56 | expect do 57 | RailsDominantColors.url('test') 58 | end.to raise_error(RailsDominantColors::Base::InvalidUrl) 59 | 60 | expect do 61 | RailsDominantColors.url('https://test.test.com') 62 | end.to raise_error(RailsDominantColors::Base::UrlNotFound) 63 | end 64 | end 65 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # RailsDominantColors 2 | 3 | [![Gem Version](https://badge.fury.io/rb/rails_dominant_colors.svg)](https://badge.fury.io/rb/rails_dominant_colors) 4 | [![Maintainability](https://api.codeclimate.com/v1/badges/83326d81b9112b45768f/maintainability)](https://codeclimate.com/github/OpenGems/rails_dominant_colors/maintainability) 5 | [![Build Status](https://travis-ci.org/OpenGems/rails_dominant_colors.svg?branch=master)](https://travis-ci.org/OpenGems/rails_dominant_colors) 6 | [![security](https://hakiri.io/github/OpenGems/rails_dominant_colors/master.svg)](https://hakiri.io/github/OpenGems/rails_dominant_colors/master) 7 | ![Gem](https://img.shields.io/gem/dt/rails_dominant_colors) 8 | [![Coverage Status](https://coveralls.io/repos/github/OpenGems/rails_dominant_colors/badge.svg?branch=master)](https://coveralls.io/github/OpenGems/rails_dominant_colors?branch=master) 9 | 10 | A Ruby gem for extract the dominant color(s) from an image (remote image, locally image, base64 image etc...) 11 | 12 | Get HEX(A) color(s), RGB(A) color(s) and HSL(A) color(s) 13 | 14 | ## Installation 15 | 16 | Add this line to your application's Gemfile: 17 | 18 | ```ruby 19 | gem 'rails_dominant_colors' 20 | ``` 21 | 22 | And then execute: 23 | 24 | $ bundle install 25 | 26 | Or install it yourself as: 27 | 28 | $ gem install rails_dominant_colors 29 | 30 | ## Usage 31 | 32 | ```ruby 33 | colors = 5 # Optional, number of colors (default: 5) 34 | 35 | dominant_colors = RailsDominantColors::Base64.new('data:image/png;base64,awesome-image', colors) 36 | 37 | # or 38 | 39 | RailsDominantColors::Url.new('https://awesome-url.com/awesome-image.png', colors) 40 | 41 | # or 42 | 43 | RailsDominantColors::Path.new('/awesome/path/awesome-image.png', colors) 44 | 45 | # and 46 | 47 | dominant_colors.to_hex # => ["#FF007F", "#000000", "#1C000F", "#A0004F", "#000000"] 48 | dominant_colors.to_hex_alpha # => ["#FF007FFF", "#00000000", "#1C000FFE", "#A0004FFF", "#0000005F"] 49 | 50 | dominant_colors.to_rgb # => [[255, 0, 127], [0, 0, 0], [28, 0, 15], [160, 0, 79], [0, 0, 0]] 51 | dominant_colors.to_rgb_alpha # => [[255, 0, 127, 1.0], [0, 0, 0, 0.0], [28, 0, 15, 1.0], [160, 0, 79, 1.0], [0, 0, 0, 0.37]] 52 | 53 | dominant_colors.to_hsl # => [[330, 100, 50], [0, 0, 0], [328, 100, 5], [330, 100, 31], [0, 0, 0]] 54 | dominant_colors.to_hsl_alpha # => [[330, 100, 50, 1.0], [0, 0, 0, 0.0], [328, 100, 5, 1.0], [330, 100, 31, 1.0], [0, 0, 0, 0.37]] 55 | 56 | # Awesome bonus 57 | dominant_colors.to_pct # => [52.16, 37.45, 9.93, 0.35, 0.1] 58 | 59 | ``` 60 | 61 | 62 | ## Contributing 63 | 64 | Bug reports and pull requests are welcome on GitHub at https://github.com/OpenGems/rails_dominant_colors. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the Contributor Covenant code of conduct. 65 | 66 | ## License 67 | 68 | The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT). 69 | -------------------------------------------------------------------------------- /lib/rails_dominant_colors/base.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'mini_magick' 4 | 5 | module RailsDominantColors 6 | class Base 7 | InvalidBase64 = Class.new(StandardError) 8 | FileNotFound = Class.new(StandardError) 9 | UrlNotFound = Class.new(StandardError) 10 | InvalidUrl = Class.new(StandardError) 11 | NotAnImage = Class.new(StandardError) 12 | EmptySource = Class.new(StandardError) 13 | 14 | attr_reader(:source, :colors) 15 | 16 | def initialize(source, colors) 17 | @source = source 18 | @colors = colors 19 | end 20 | 21 | def to_hex_alpha 22 | @to_hex_alpha ||= sort_by_size.map { |x| x[:hex] } 23 | end 24 | 25 | def to_hex 26 | @to_hex ||= sort_by_size.map { |x| x[:hex][0..6] } 27 | end 28 | 29 | def to_rgb_alpha 30 | @to_rgb_alpha ||= sort_by_size.map { |x| [x[:r], x[:g], x[:b], x[:a]] } 31 | end 32 | 33 | def to_rgb 34 | @to_rgb ||= sort_by_size.map { |x| [x[:r], x[:g], x[:b]] } 35 | end 36 | 37 | def to_hsl_alpha 38 | @to_hsl_alpha ||= sort_by_size.map do |x| 39 | rgb_to_hsl( 40 | x[:r] / 255.0, 41 | x[:g] / 255.0, 42 | x[:b] / 255.0, 43 | x[:a] 44 | ) 45 | end 46 | end 47 | 48 | def to_hsl 49 | @to_hsl ||= sort_by_size.map do |x| 50 | rgb_to_hsl( 51 | x[:r] / 255.0, 52 | x[:g] / 255.0, 53 | x[:b] / 255.0 54 | ) 55 | end 56 | end 57 | 58 | def to_pct 59 | @to_pct ||= sort_by_size.map { |x| ((x[:size].to_f / total) * 100).round(2) } 60 | end 61 | 62 | private 63 | 64 | def extensions 65 | @extensions ||= { 66 | 'image/png': '.png', 67 | 'image/jpeg': '.jpg', 68 | 'image/jpg': '.jpg', 69 | 'image/gif': '.gif', 70 | 'image/bmp': '.bmp', 71 | 'image/tiff': '.tif' 72 | } 73 | end 74 | 75 | def image 76 | @image ||= ::MiniMagick::Image.open(source) 77 | end 78 | 79 | def execute 80 | @execute ||= MiniMagick.convert do |convert| 81 | convert << image.path 82 | convert << '-format' << '%c' 83 | convert << '-colors' << colors.to_s 84 | convert << '-depth' << '8' 85 | convert << '-alpha' << 'on' 86 | convert << 'histogram:info:' 87 | end 88 | end 89 | 90 | def sort_by_size 91 | @sort_by_size ||= parse.sort_by { |x| x[:size] }.reverse 92 | end 93 | 94 | def total 95 | @total ||= sort_by_size.map { |x| x[:size] }.reduce(:+) 96 | end 97 | 98 | def parse 99 | @parse ||= execute.split("\n").map { |l| l.gsub(%r{\s}, '') }.map do |l| 100 | match = l.match( 101 | %r{(?[0-9]+):\((?[0-9]+),(?[0-9]+),(?[0-9]+)[,]*(?[0-9]+)\)(?#[0-9A-F]+)} 102 | ) 103 | format_color(match) 104 | end 105 | end 106 | 107 | def format_color(color) 108 | { 109 | size: color[:size].to_i, 110 | r: color[:r].to_i, 111 | g: color[:g].to_i, 112 | b: color[:b].to_i, 113 | a: (color[:a].to_f / 255).round(2), 114 | hex: color[:hex] 115 | } 116 | end 117 | 118 | def rgb_to_hsl(r, g, b, a = nil) 119 | min, max = min_max(r, g, b) 120 | 121 | l = (max + min) / 2.0 122 | 123 | if max == min 124 | s = h = 0 125 | else 126 | d = max - min 127 | s = l >= 0.5 ? d / (2.0 - max - min) : d / (max + min) 128 | h = case max 129 | when r then (g - b) / d + (g < b ? 6.0 : 0) 130 | when g then (b - r) / d + 2.0 131 | when b then (r - g) / d + 4.0 132 | end 133 | h /= 6.0 134 | end 135 | 136 | format_hsl(h, s, l, a) 137 | end 138 | 139 | def min_max(r, g, b) 140 | max = [r, g, b].max 141 | min = [r, g, b].min 142 | [min, max] 143 | end 144 | 145 | def format_hsl(h, s, l, a) 146 | [(h * 360).round, (s * 100).round, (l * 100).round, a].compact 147 | end 148 | end 149 | end 150 | --------------------------------------------------------------------------------