imagga 0.0.3 → 0.0.5

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 (41) hide show
  1. checksums.yaml +15 -0
  2. data/README.md +28 -1
  3. data/imagga.gemspec +1 -0
  4. data/lib/imagga.rb +8 -3
  5. data/lib/imagga/client.rb +12 -34
  6. data/lib/imagga/commands.rb +42 -0
  7. data/lib/imagga/core_client.rb +25 -0
  8. data/lib/imagga/exceptions.rb +3 -0
  9. data/lib/imagga/image.rb +59 -2
  10. data/lib/imagga/image_or_url_parametizer.rb +21 -0
  11. data/lib/imagga/options.rb +74 -0
  12. data/lib/imagga/parametizer.rb +18 -0
  13. data/lib/imagga/rank_color_parametizer.rb +17 -0
  14. data/lib/imagga/resolution_parametizer.rb +17 -0
  15. data/lib/imagga/result_builder.rb +27 -0
  16. data/lib/imagga/version.rb +1 -1
  17. data/spec/fixtures/crop_response.txt +62 -0
  18. data/spec/fixtures/extract_response.txt +253 -0
  19. data/spec/fixtures/rank_response.txt +12 -0
  20. data/spec/integration/crop_spec.rb +60 -0
  21. data/spec/integration/extract_spec.rb +51 -0
  22. data/spec/integration/rank_spec.rb +103 -0
  23. data/spec/lib/commands_spec.rb +70 -0
  24. data/spec/lib/core_client_spec.rb +43 -0
  25. data/spec/lib/crop_info_spec.rb +15 -0
  26. data/spec/lib/crop_options_spec.rb +24 -0
  27. data/spec/lib/crop_result_builder_spec.rb +4 -0
  28. data/spec/lib/extraction_options_spec.rb +16 -51
  29. data/spec/lib/image_crop_spec.rb +34 -0
  30. data/spec/lib/image_or_url_parametizer_spec.rb +65 -0
  31. data/spec/lib/parametizer_spec.rb +42 -0
  32. data/spec/lib/rank_color_parametizer_spec.rb +27 -0
  33. data/spec/lib/rank_color_spec.rb +51 -0
  34. data/spec/lib/rank_options_spec.rb +28 -0
  35. data/spec/lib/rank_result_builder_spec.rb +1 -12
  36. data/spec/lib/resolution_parametizer_spec.rb +60 -0
  37. metadata +63 -28
  38. data/lib/imagga/extract_options.rb +0 -74
  39. data/lib/imagga/extract_result_builder.rb +0 -11
  40. data/lib/imagga/rank_result_builder.rb +0 -9
  41. data/spec/lib/client_spec.rb +0 -352
@@ -0,0 +1,15 @@
1
+ ---
2
+ !binary "U0hBMQ==":
3
+ metadata.gz: !binary |-
4
+ N2ViMjRjNTFlZmE3YTNmYzc4ZTEwMjlmZDg5NjJiNzRkNGRjOGViNg==
5
+ data.tar.gz: !binary |-
6
+ NGMyOTRhMzEzMTkzZmQ5MjJmMTkxZGJiOTBiYzViM2IwZTE0Njk1Mg==
7
+ !binary "U0hBNTEy":
8
+ metadata.gz: !binary |-
9
+ ZDc1MWI4N2NmOGQ0N2FlZTQ2NjJjZDRkMzA0ZDhiYzAyNjcwMmNlNmI0OGY3
10
+ MjY2ZjVlOTcwMzhiZWRmMmI0N2YyYjU1MDQxYWU4MWI3YjM5ZjM2MzU2NjI4
11
+ MDAwNTQ5NGUxMDRmZWE4MmRjNTkzMGE2OTljZGIwM2ZiZWQ2MTg=
12
+ data.tar.gz: !binary |-
13
+ YTc1ODMzZGVlOTgyZDAwM2JlMmZiNjNiMzNlMWZkMWYyNDdmNGY2MGY4NTJh
14
+ MjE4ZDI2M2FiNzRhOTNmMWY0N2FjYTQ0YWJmNTFlMWQ0ZTNmNjY3YzMxZWNh
15
+ MzlhYTkwMmQ5NTgwZTJiZjRhNWUzNDBiNTRhZjZmM2U5YTJkNTM=
data/README.md CHANGED
@@ -1,3 +1,4 @@
1
+ [![Code Climate](https://codeclimate.com/github/martkaru/imagga.png)](https://codeclimate.com/github/martkaru/imagga)
1
2
  [![Build Status](https://travis-ci.org/martkaru/imagga.png?branch=master)](https://travis-ci.org/martkaru/imagga)
2
3
 
3
4
  # Imagga
@@ -56,7 +57,10 @@ Check the results, for example:
56
57
  Multi-color search:
57
58
 
58
59
  client.rank(
59
- color_vector: '20,82,37,43,30,20,30,10', # vector of colors (percentage, r, g, b)
60
+ colors: [
61
+ Imagga::RankColor.new(percent: 100, r: 82, g: 37, b: 43) # use r g b values
62
+ Imagga::RankColor.new(percent: 100, hex: '#336699') # or use hex
63
+ ],
60
64
  type: 'object',
61
65
  dist: 6000,
62
66
  count: 10
@@ -64,6 +68,29 @@ Multi-color search:
64
68
  puts "Distance of #%i is %.4f" % [similarity.id, similarity.dist] # Distance of #333 is 3581.5500
65
69
  end
66
70
 
71
+ Image crop suggestions:
72
+
73
+ client.crop(
74
+ [
75
+ 'http://image1', # Use urls
76
+ Imagga::Image.new(url: 'http://image2') # or image object
77
+ ],
78
+ resolutions: ['100x40','50x100'], # '100,40,50,100' or '100x40,50x100' instead of array would work also
79
+ no_scaling: true
80
+ ).each do |crop_info|
81
+ puts crop_info.url
82
+ crop_info.croppings.each do |cropping|
83
+ puts cropping.info
84
+ end
85
+ end
86
+
87
+ ## Contributors
88
+
89
+ martkaru
90
+
91
+ jprosevear
92
+
93
+
67
94
  ## Contributing
68
95
 
69
96
  1. Fork it
@@ -18,6 +18,7 @@ Gem::Specification.new do |gem|
18
18
 
19
19
  gem.add_dependency "httparty", "~> 0.10"
20
20
  gem.add_dependency "json", "~> 1.7"
21
+ gem.add_dependency "color", "~> 1.4.1"
21
22
 
22
23
  gem.add_development_dependency "rspec", "~> 2.12"
23
24
  gem.add_development_dependency "rake"
@@ -1,7 +1,12 @@
1
1
  require "imagga/version"
2
2
  require "imagga/exceptions"
3
- require "imagga/extract_result_builder"
4
- require "imagga/rank_result_builder"
5
3
  require "imagga/image"
6
- require "imagga/extract_options"
4
+ require "imagga/result_builder"
5
+ require "imagga/parametizer"
6
+ require "imagga/image_or_url_parametizer"
7
+ require "imagga/rank_color_parametizer"
8
+ require "imagga/resolution_parametizer"
9
+ require "imagga/commands"
10
+ require "imagga/core_client"
11
+ require "imagga/options"
7
12
  require "imagga/client"
@@ -1,42 +1,20 @@
1
- require "httparty"
2
- require "json"
3
-
4
1
  module Imagga
5
- class Client
6
- include HTTParty
7
- include Imagga::Exceptions
8
- attr_reader :api_key, :api_secret, :base_uri, :service_path
9
-
10
- def initialize(opts={})
11
- @api_key = opts[:api_key] || raise_missing(:api_key)
12
- @api_secret = opts[:api_secret] || raise_missing(:api_secret)
13
- @base_uri = opts[:base_uri] || raise_missing(:base_uri)
14
- @service_path = '/colorsearchserver.php'
15
-
16
- Imagga::Client.base_uri @base_uri
17
- end
18
-
19
- def extract(urls_or_images, additional_options={})
20
- options = extract_options(urls_or_images, additional_options)
21
- result = JSON.parse(self.class.post(service_path, body: options))
22
- raise_if_request_failed!(result)
23
- ExtractResultBuilder.new.build_from(result)
24
- end
25
-
26
- def rank(opts)
27
- result = JSON.parse(self.class.post(service_path, body: rank_options(opts)))
28
- raise_if_request_failed!(result)
29
- RankResultBuilder.new.build_from(result)
2
+ class Client < CoreClient
3
+ def extract(urls_or_images, options={})
4
+ options.merge!(ImageOrUrlParametizer.new.parametrize(urls_or_images))
5
+ ExtractResultBuilder.new.build_from(super(options))
30
6
  end
31
7
 
32
- private
33
-
34
- def extract_options(urls_or_images, additional_options)
35
- ExtractOptions.new(api_key, api_secret).options(urls_or_images, additional_options)
8
+ def rank(options={})
9
+ colors = options.delete(:colors) { raise_missing('colors') }
10
+ options.merge!(RankColorParametizer.new.parametrize(colors))
11
+ RankResultBuilder.new.build_from(super(options))
36
12
  end
37
13
 
38
- def rank_options(opts)
39
- RankOptions.new(api_key, api_secret).options(opts)
14
+ def crop(urls_or_images, options={})
15
+ options.merge!(ImageOrUrlParametizer.new.build_urls(urls_or_images))
16
+ options.merge!(ResolutionParametizer.new.parametrize(options.fetch(:resolutions)))
17
+ CropResultBuilder.new.build_from(super(options))
40
18
  end
41
19
  end
42
20
  end
@@ -0,0 +1,42 @@
1
+ require "httparty"
2
+ require "json"
3
+
4
+ module Imagga
5
+ class BaseCommand
6
+ include HTTParty
7
+ include Imagga::Exceptions
8
+
9
+ attr_reader :api_key, :api_secret, :base_uri
10
+
11
+ def initialize(api_key, api_secret, base_uri)
12
+ @api_key, @api_secret, @base_uri = api_key, api_secret, base_uri
13
+ self.class.base_uri @base_uri
14
+ end
15
+
16
+ def execute(options)
17
+ JSON.parse(self.class.post(service_path, body: args(options))).tap do |result|
18
+ raise_if_request_failed!(result)
19
+ end
20
+ end
21
+
22
+ def args(options)
23
+ options_class.new(api_key, api_secret).options(options)
24
+ end
25
+
26
+ def options_class; BaseOptions; end
27
+ end
28
+
29
+ class ExtractCommand < BaseCommand
30
+ def service_path; '/colorsearchserver.php'; end
31
+ def options_class; ExtractOptions; end
32
+ end
33
+
34
+ class RankCommand < ExtractCommand
35
+ def options_class; RankOptions; end
36
+ end
37
+
38
+ class CropCommand < BaseCommand
39
+ def service_path; '/extractionrestserver.php'; end
40
+ def options_class; CropOptions; end
41
+ end
42
+ end
@@ -0,0 +1,25 @@
1
+ module Imagga
2
+ class CoreClient
3
+ include Imagga::Exceptions
4
+ attr_reader :api_key, :api_secret, :base_uri
5
+
6
+ def initialize(opts={})
7
+ @api_key = opts[:api_key] || raise_missing(:api_key)
8
+ @api_secret = opts[:api_secret] || raise_missing(:api_secret)
9
+ @base_uri = opts[:base_uri] || raise_missing(:base_uri)
10
+ end
11
+
12
+ def extract(options={})
13
+ ExtractCommand.new(api_key, api_secret, base_uri).execute(options)
14
+ end
15
+
16
+ def rank(options={})
17
+ RankCommand.new(api_key, api_secret, base_uri).execute(options)
18
+ end
19
+
20
+ def crop(options={})
21
+ CropCommand.new(api_key, api_secret, base_uri).execute(options)
22
+ end
23
+ end
24
+ end
25
+
@@ -1,5 +1,6 @@
1
1
  module Imagga
2
2
  module Exceptions
3
+
3
4
  def raise_missing(attribute)
4
5
  raise ArgumentError, "%s is missing" % attribute.to_s
5
6
  end
@@ -9,10 +10,12 @@ module Imagga
9
10
  raise Imagga::ClientException.new(result['error_code'].to_i), result['error_message'], caller[0..-1]
10
11
  end
11
12
  end
13
+
12
14
  end
13
15
 
14
16
  class ClientException < StandardError
15
17
  attr_accessor :error_code
18
+
16
19
  def initialize(error_code)
17
20
  @error_code = error_code
18
21
  super
@@ -1,11 +1,13 @@
1
+ require 'color'
2
+
1
3
  module Imagga
2
4
  class Image
3
5
  include Imagga::Exceptions
4
6
  attr_accessor :url, :id
5
7
 
6
8
  def initialize(opts)
7
- @url = opts[:url] || raise_missing(:url)
8
- @id = opts[:id] || 0
9
+ @url = opts[:url] || raise_missing(:url)
10
+ @id = opts[:id] || 0
9
11
  end
10
12
  end
11
13
 
@@ -64,6 +66,20 @@ module Imagga
64
66
  end
65
67
  end
66
68
 
69
+ class RankColor
70
+ attr_accessor :percent, :r, :g, :b, :hex
71
+ def initialize(opts)
72
+ @percent = opts.fetch(:percent)
73
+ if @hex = opts[:hex]
74
+ color = Color::RGB.from_html(@hex)
75
+ @r, @g, @b = color.red, color.green, color.blue
76
+ else
77
+ @r, @g, @b = opts[:r], opts[:g], opts[:b]
78
+ @hex = Color::RGB.new(r, g, b).html
79
+ end
80
+ end
81
+ end
82
+
67
83
  class RankSimilarity < ImageInfoBase
68
84
  def self.fields
69
85
  %w(id dist)
@@ -71,4 +87,45 @@ module Imagga
71
87
 
72
88
  attr_accessor *fields
73
89
  end
90
+
91
+ class CropInfo < ImageInfoBase
92
+ def self.fields
93
+ %w(url)
94
+ end
95
+
96
+ def self.crop_fields
97
+ %w(croppings)
98
+ end
99
+
100
+ def initialize(opts={})
101
+ super({'url' => opts['url']})
102
+ self.class.crop_fields.each do |field|
103
+ send("#{field}=", build_image_crops(opts[field]))
104
+ end
105
+ end
106
+
107
+ def build_image_crops(image_crop_nodes)
108
+ image_crop_nodes && image_crop_nodes.map { |crop_node| ImageCrop.new(crop_node) }
109
+ end
110
+
111
+ attr_accessor *(fields + crop_fields)
112
+ end
113
+
114
+ class ImageCrop < ImageInfoBase
115
+ def self.fields
116
+ %w(target_width target_height x1 y1 x2 y2)
117
+ end
118
+
119
+ attr_accessor *fields
120
+
121
+ %w(target_width target_height x1 y1 x2 y2).each do |field|
122
+ define_method("#{field}=") do |value|
123
+ instance_variable_set("@#{field}", value.to_i)
124
+ end
125
+ end
126
+
127
+ def info
128
+ "target: (%i,%i), crop: (%i, %i) to (%i, %i)" % [target_width, target_height, x1, y1, x2, y2]
129
+ end
130
+ end
74
131
  end
@@ -0,0 +1,21 @@
1
+ module Imagga
2
+ class ImageOrUrlParametizer
3
+ include Parametizer
4
+
5
+ def parametrize(urls_or_images)
6
+ options = build_urls(urls_or_images)
7
+ if (ids = build_ids(urls_or_images))[:ids]
8
+ options.merge!(ids)
9
+ end
10
+ options
11
+ end
12
+
13
+ def build_urls(urls_or_images)
14
+ { urls: [urls_or_images].flatten.map{ |o| o.url rescue o }.join(',') }
15
+ end
16
+
17
+ def build_ids(urls_or_images)
18
+ { ids: build_comma_separated_string(urls_or_images, :id, 0) }
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,74 @@
1
+ module Imagga
2
+ class BaseOptions
3
+ include Parametizer
4
+ attr_accessor :api_key, :api_secret, :version
5
+
6
+ def initialize(api_key, api_secret)
7
+ @version = '1.0'
8
+ @api_key = api_key || raise_missing(:api_key)
9
+ @api_secret = api_secret || raise_missing(:api_secret)
10
+ end
11
+
12
+ def base_options
13
+ { v: version, api_key: api_key }
14
+ end
15
+
16
+ def sign(options)
17
+ sorted_options_string = options.keys.sort.map do |key|
18
+ "%s=%s" % [key.to_s, options[key]]
19
+ end.join('') << api_secret
20
+ Digest::MD5.hexdigest(sorted_options_string)
21
+ end
22
+
23
+ def options(opts={})
24
+ {}
25
+ end
26
+ end
27
+
28
+ class ExtractOptions < BaseOptions
29
+ def options(opts={})
30
+ opts.merge!(base_options).merge!(method: method)
31
+ opts.merge!(build_boolean_options(opts, boolean_fields))
32
+ opts.merge!(sig: sign(opts))
33
+ end
34
+
35
+ def method
36
+ 'imagga.colorsearch.extract'
37
+ end
38
+
39
+ def boolean_fields
40
+ [:extract_overall_colors, :extract_object_colors]
41
+ end
42
+ end
43
+
44
+ class RankOptions < BaseOptions
45
+ include Imagga::Exceptions
46
+
47
+ def options(opts={})
48
+ opts.merge!(base_options).merge!(
49
+ method: method,
50
+ color_vector: opts.delete(:color_vector),
51
+ type: (opts.delete(:type) { raise_missing('type') }).to_s,
52
+ dist: (opts.delete(:dist) { raise_missing('dist') }).to_s,
53
+ count: (opts.delete(:count) { raise_missing('count') }).to_s
54
+ )
55
+ opts.merge!(sig: sign(opts))
56
+ end
57
+
58
+ def method
59
+ 'imagga.colorsearch.rank'
60
+ end
61
+ end
62
+
63
+ class CropOptions < BaseOptions
64
+ def options(opts={})
65
+ options = base_options.merge(opts).merge(method: method)
66
+ options.merge!(build_boolean_options(opts, :no_scaling))
67
+ options.merge!(sig: sign(options))
68
+ end
69
+
70
+ def method
71
+ 'imagga.process.crop'
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,18 @@
1
+ module Imagga
2
+ module Parametizer
3
+ def build_comma_separated_string(objects, field, default_to=nil)
4
+ field_values = [objects].flatten.map{ |o| o.send(field) rescue default_to }
5
+ return if field_values.uniq == [default_to]
6
+ field_values.join(',')
7
+ end
8
+
9
+ def build_boolean_options(options, keys)
10
+ [keys].flatten.inject({}) do |result, key|
11
+ if options.keys.include?(key) && (value = [true, '1', 1].include?(options[key]) ? 1 : 0)
12
+ result[key] = value
13
+ end
14
+ result
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,17 @@
1
+ module Imagga
2
+ class RankColorParametizer
3
+ include Parametizer
4
+
5
+ def parametrize(rank_color_or_colors)
6
+ { color_vector: build_vector(rank_color_or_colors) }
7
+ end
8
+
9
+ def build_vector(rank_color_or_colors)
10
+ [rank_color_or_colors].flatten.map{ |o| build_rank_color_string(o) rescue o }.join(',')
11
+ end
12
+
13
+ def build_rank_color_string(rank_color)
14
+ "%i,%i,%i,%i" % [rank_color.percent, rank_color.r, rank_color.g, rank_color.b]
15
+ end
16
+ end
17
+ end