image_processing 1.4.0 → 1.5.0

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of image_processing might be problematic. Click here for more details.

checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 91616e1f059395f4197125f345f097948f04bdabb08737c95155856aa267885e
4
- data.tar.gz: f7d33fce924062960e4ac5dae9019df37374b6ae1ca9c0f97dc8e5fe66400860
3
+ metadata.gz: 5b28b25634ec49e61835850b56f7c1c46b4d0e10ea99fbb6ef05c238e307d348
4
+ data.tar.gz: 348ff75f2a0779c085341248ac9cf4e9a931e984a3ba2e2e016c77dd63cc04e0
5
5
  SHA512:
6
- metadata.gz: 701f320524f7753255ebf6601196dc0c3c6c81bb52ae2f3c92ccb05850e1ef6ae18e9c31dd271a0d8b3d6bd8b727b9c2e8757feda2d024df957f40126ac7799f
7
- data.tar.gz: 07c799fe3f6a430177ca426280181343315a7e17ba28d74b2feb3bf047f1d263fefa5c3663594daa733f7448872ba03ef64208fafd2e1b6c721f680dad02c58b
6
+ metadata.gz: 85f39d0e536bddda22b5a79d5a0cc95496cf8eccccb536549ac209dbad3850b83add4797420a165582c666230015a1a4f969c0846e026f950a0d4dee1c5d00a8
7
+ data.tar.gz: e9b452b98df5b6f96cccf89b51a17b6ea3d06197adce46549d441fbd6822bcecac58cb9bf8e8ddc8ad14c3b144f8a8d703cb4d183d615ad349337430f6da9b7c
data/CHANGELOG.md CHANGED
@@ -1,3 +1,9 @@
1
+ ## 1.5.0 (2018-07-10)
2
+
3
+ * [minimagick, vips] Add `#composite` method (@janko-m)
4
+
5
+ * [core] Allow operations to accept blocks (janko-m)
6
+
1
7
  ## 1.4.0 (2018-06-14)
2
8
 
3
9
  * [minimagick] Accept RGB(A) arrays for color names for `:background` (@janko-m)
@@ -8,7 +14,7 @@
8
14
 
9
15
  ## 1.3.0 (2018-06-13)
10
16
 
11
- * [minimagick, vips] Add `#rotate` function (@janko-m)
17
+ * [minimagick, vips] Add `#rotate` method (@janko-m)
12
18
 
13
19
  * [vips] Use native `vips_image_hasalpha()` and `vips_addalpha()` functions in `#resize_and_pad` (@janko-m)
14
20
 
@@ -16,10 +16,6 @@ module ImageProcessing
16
16
  branch saver: options
17
17
  end
18
18
 
19
- def custom(&block)
20
- operation :custom, block
21
- end
22
-
23
19
  def apply(operations)
24
20
  operations.inject(self) do |builder, (name, argument)|
25
21
  if argument == true || argument == nil
@@ -32,15 +28,15 @@ module ImageProcessing
32
28
  end
33
29
  end
34
30
 
35
- def method_missing(name, *args)
31
+ def method_missing(name, *args, &block)
36
32
  return super if name.to_s.end_with?("?")
37
- return send(name.to_s.chomp("!"), *args).call if name.to_s.end_with?("!")
33
+ return send(name.to_s.chomp("!"), *args, &block).call if name.to_s.end_with?("!")
38
34
 
39
- operation name, *args
35
+ operation(name, *args, &block)
40
36
  end
41
37
 
42
- def operation(name, *args)
43
- branch operations: [[name, args]]
38
+ def operation(name, *args, &block)
39
+ branch operations: [[name, args, *block]]
44
40
  end
45
41
 
46
42
  def call(file = nil, destination: nil, **call_options)
@@ -57,7 +53,7 @@ module ImageProcessing
57
53
  options = options.merge(loader: options[:loader].merge(loader)) if loader
58
54
  options = options.merge(saver: options[:saver].merge(saver)) if saver
59
55
  options = options.merge(operations: options[:operations] + operations) if operations
60
- options = options.merge(processor_class: self::Processor) unless self.is_a?(Builder)
56
+ options = options.merge(processor: self::Processor) unless self.is_a?(Builder)
61
57
  options = options.merge(other_options)
62
58
 
63
59
  options.freeze
@@ -66,12 +62,12 @@ module ImageProcessing
66
62
  end
67
63
 
68
64
  DEFAULT_OPTIONS = {
69
- source: nil,
70
- loader: {},
71
- saver: {},
72
- format: nil,
73
- operations: [],
74
- processor_class: nil,
65
+ source: nil,
66
+ loader: {},
67
+ saver: {},
68
+ format: nil,
69
+ operations: [],
70
+ processor: nil,
75
71
  }.freeze
76
72
  end
77
73
  end
@@ -16,88 +16,98 @@ module ImageProcessing
16
16
  end
17
17
 
18
18
  class Processor < ImageProcessing::Processor
19
- IMAGE_CLASS = ::MiniMagick::Tool
19
+ accumulator :magick, ::MiniMagick::Tool
20
+
20
21
  SHARPEN_PARAMETERS = { radius: 0, sigma: 1 }
21
22
 
22
- def resize_to_limit(magick, width, height, **options)
23
- thumbnail(magick, "#{width}x#{height}>", **options)
23
+ def self.load_image(path_or_magick, page: nil, geometry: nil, auto_orient: true, **options)
24
+ if path_or_magick.is_a?(::MiniMagick::Tool)
25
+ magick = path_or_magick
26
+ else
27
+ source_path = path_or_magick
28
+ magick = ::MiniMagick::Tool::Convert.new
29
+
30
+ Utils.apply_options(magick, options)
31
+
32
+ input_path = source_path
33
+ input_path += "[#{page}]" if page
34
+ input_path += "[#{geometry}]" if geometry
35
+
36
+ magick << input_path
37
+ end
38
+
39
+ magick.auto_orient if auto_orient
40
+ magick
24
41
  end
25
42
 
26
- def resize_to_fit(magick, width, height, **options)
27
- thumbnail(magick, "#{width}x#{height}", **options)
43
+ def self.save_image(magick, destination_path, allow_splitting: false, **options)
44
+ Utils.apply_options(magick, options)
45
+
46
+ magick << destination_path
47
+ magick.call
48
+
49
+ Utils.disallow_split_layers!(destination_path) unless allow_splitting
50
+ end
51
+
52
+ def resize_to_limit(width, height, **options)
53
+ thumbnail("#{width}x#{height}>", **options)
28
54
  end
29
55
 
30
- def resize_to_fill(magick, width, height, gravity: "Center", **options)
31
- thumbnail(magick, "#{width}x#{height}^", **options)
56
+ def resize_to_fit(width, height, **options)
57
+ thumbnail("#{width}x#{height}", **options)
58
+ end
59
+
60
+ def resize_to_fill(width, height, gravity: "Center", **options)
61
+ thumbnail("#{width}x#{height}^", **options)
32
62
  magick.gravity gravity
33
63
  magick.background color(:transparent)
34
64
  magick.extent "#{width}x#{height}"
35
65
  end
36
66
 
37
- def resize_and_pad(magick, width, height, background: :transparent, gravity: "Center", **options)
38
- thumbnail(magick, "#{width}x#{height}", **options)
67
+ def resize_and_pad(width, height, background: :transparent, gravity: "Center", **options)
68
+ thumbnail("#{width}x#{height}", **options)
39
69
  magick.background color(background)
40
70
  magick.gravity gravity
41
71
  magick.extent "#{width}x#{height}"
42
72
  end
43
73
 
44
- def rotate(magick, degrees, background: nil)
74
+ def rotate(degrees, background: nil)
45
75
  magick.background color(background) if background
46
76
  magick.rotate(degrees)
47
77
  end
48
78
 
49
- def define(magick, options)
50
- return magick.define(options) if options.is_a?(String)
79
+ def composite(overlay = :none, mask: nil, compose: nil, gravity: nil, geometry: nil, args: nil, &block)
80
+ return magick.composite if overlay == :none
51
81
 
52
- options.each do |namespace, settings|
53
- namespace = namespace.to_s.gsub("_", "-")
82
+ overlay_path = convert_to_path(overlay, "overlay")
83
+ mask_path = convert_to_path(mask, "mask") if mask
54
84
 
55
- settings.each do |key, value|
56
- key = key.to_s.gsub("_", "-")
85
+ magick << overlay_path
86
+ magick << mask_path if mask_path
57
87
 
58
- magick.define "#{namespace}:#{key}=#{value}"
59
- end
60
- end
88
+ magick.compose(compose) if compose
89
+ define(compose: { args: args }) if args
61
90
 
62
- magick
63
- end
91
+ magick.gravity(gravity) if gravity
92
+ magick.geometry(geometry) if geometry
64
93
 
65
- def limits(magick, options)
66
- limit_args = options.flat_map { |type, value| %W[-limit #{type} #{value}] }
67
- prepend_args(magick, limit_args)
68
- end
94
+ yield magick if block_given?
69
95
 
70
- def append(magick, *args)
71
- magick.merge! args
96
+ magick.composite
72
97
  end
73
98
 
74
- def load_image(path_or_magick, page: nil, geometry: nil, auto_orient: true, **options)
75
- if path_or_magick.is_a?(::MiniMagick::Tool)
76
- magick = path_or_magick
77
- else
78
- source_path = path_or_magick
79
- magick = ::MiniMagick::Tool::Convert.new
80
-
81
- apply_options(magick, options)
82
-
83
- input_path = source_path
84
- input_path += "[#{page}]" if page
85
- input_path += "[#{geometry}]" if geometry
86
-
87
- magick << input_path
88
- end
99
+ def define(options)
100
+ return magick.define(options) if options.is_a?(String)
101
+ Utils.apply_define(magick, options)
102
+ end
89
103
 
90
- magick.auto_orient if auto_orient
104
+ def limits(options)
105
+ options.each { |type, value| magick.args.unshift("-limit", type.to_s, value.to_s) }
91
106
  magick
92
107
  end
93
108
 
94
- def save_image(magick, destination_path, allow_splitting: false, **options)
95
- apply_options(magick, options)
96
-
97
- magick << destination_path
98
- magick.call
99
-
100
- disallow_split_layers!(destination_path) unless allow_splitting
109
+ def append(*args)
110
+ magick.merge! args
101
111
  end
102
112
 
103
113
  private
@@ -111,42 +121,65 @@ module ImageProcessing
111
121
  raise ArgumentError, "unrecognized color format: #{value.inspect} (must be one of: string, 3-element RGB array, 4-element RGBA array)"
112
122
  end
113
123
 
114
- def thumbnail(magick, geometry, sharpen: {})
124
+ def thumbnail(geometry, sharpen: {})
115
125
  magick.resize(geometry)
116
- magick.sharpen(sharpen_value(sharpen)) if sharpen
126
+
127
+ if sharpen
128
+ sharpen = SHARPEN_PARAMETERS.merge(sharpen)
129
+ magick.sharpen("#{sharpen[:radius]}x#{sharpen[:sigma]}")
130
+ end
131
+
117
132
  magick
118
133
  end
119
134
 
120
- def sharpen_value(parameters)
121
- parameters = SHARPEN_PARAMETERS.merge(parameters)
122
- radius, sigma = parameters.values_at(:radius, :sigma)
123
-
124
- "#{radius}x#{sigma}"
135
+ def convert_to_path(file, name)
136
+ if file.is_a?(String)
137
+ file
138
+ elsif file.respond_to?(:to_path)
139
+ file.to_path
140
+ elsif file.respond_to?(:path)
141
+ file.path
142
+ else
143
+ raise ArgumentError, "#{name} must be a String, Pathname, or respond to #path"
144
+ end
125
145
  end
126
146
 
127
- def apply_options(magick, define: {}, **options)
128
- options.each do |option, value|
129
- case value
130
- when true, nil then magick.send(option)
131
- when false then magick.send(option).+
132
- else magick.send(option, *value)
147
+ module Utils
148
+ module_function
149
+
150
+ def disallow_split_layers!(destination_path)
151
+ layers = Dir[destination_path.sub(/\.\w+$/, '-*\0')]
152
+
153
+ if layers.any?
154
+ layers.each { |path| File.delete(path) }
155
+ raise Error, "Multi-layer image is being converted into a single-layer format. You should either process individual layers or set :allow_splitting to true. See https://github.com/janko-m/image_processing/wiki/Splitting-a-PDF-into-multiple-images for how to process each layer individually."
133
156
  end
134
157
  end
135
158
 
136
- define(magick, define)
137
- end
159
+ def apply_options(magick, define: {}, **options)
160
+ options.each do |option, value|
161
+ case value
162
+ when true, nil then magick.send(option)
163
+ when false then magick.send(option).+
164
+ else magick.send(option, *value)
165
+ end
166
+ end
138
167
 
139
- def prepend_args(magick, args)
140
- magick.args.replace args + magick.args
141
- magick
142
- end
168
+ apply_define(magick, define)
169
+ end
170
+
171
+ def apply_define(magick, options)
172
+ options.each do |namespace, settings|
173
+ namespace = namespace.to_s.gsub("_", "-")
143
174
 
144
- def disallow_split_layers!(destination_path)
145
- layers = Dir[destination_path.sub(/\.\w+$/, '-*\0')]
175
+ settings.each do |key, value|
176
+ key = key.to_s.gsub("_", "-")
177
+
178
+ magick.define "#{namespace}:#{key}=#{value}"
179
+ end
180
+ end
146
181
 
147
- if layers.any?
148
- layers.each { |path| File.delete(path) }
149
- raise Error, "Multi-layer image is being converted into a single-layer format. You should either process individual layers or set :allow_splitting to true. See https://github.com/janko-m/image_processing/wiki/Splitting-a-PDF-into-multiple-images for how to process each layer individually."
182
+ magick
150
183
  end
151
184
  end
152
185
  end
@@ -4,7 +4,7 @@ module ImageProcessing
4
4
  class Pipeline
5
5
  DEFAULT_FORMAT = "jpg"
6
6
 
7
- attr_reader :source, :loader, :saver, :format, :operations, :processor_class, :destination
7
+ attr_reader :source, :loader, :saver, :format, :operations, :processor, :destination
8
8
 
9
9
  def initialize(options)
10
10
  options.each do |name, value|
@@ -14,22 +14,21 @@ module ImageProcessing
14
14
  end
15
15
 
16
16
  def call(save: true)
17
- processor = processor_class.new(self)
18
- image = processor.load_image(source, **loader)
17
+ accumulator = processor.load_image(source, **loader)
19
18
 
20
- operations.each do |name, args|
21
- image = processor.apply_operation(name, image, *args)
19
+ operations.each do |name, args, block|
20
+ accumulator = processor.apply_operation(accumulator, name, *args, &block)
22
21
  end
23
22
 
24
23
  if save == false
25
- image
24
+ accumulator
26
25
  elsif destination
27
26
  handle_destination do
28
- processor.save_image(image, destination, **saver)
27
+ processor.save_image(accumulator, destination, **saver)
29
28
  end
30
29
  else
31
30
  create_tempfile do |tempfile|
32
- processor.save_image(image, tempfile.path, **saver)
31
+ processor.save_image(accumulator, tempfile.path, **saver)
33
32
  end
34
33
  end
35
34
  end
@@ -74,9 +73,9 @@ module ImageProcessing
74
73
  def normalize_source(source, options)
75
74
  fail Error, "source file is not provided" unless source
76
75
 
77
- image_class = options[:processor_class]::IMAGE_CLASS
76
+ accumulator_class = options[:processor]::ACCUMULATOR_CLASS
78
77
 
79
- if source.is_a?(image_class)
78
+ if source.is_a?(accumulator_class)
80
79
  source
81
80
  elsif source.is_a?(String)
82
81
  source
@@ -85,7 +84,7 @@ module ImageProcessing
85
84
  elsif source.respond_to?(:to_path)
86
85
  source.to_path
87
86
  else
88
- fail Error, "source file needs to respond to #path, or be a String, a Pathname, or a #{image_class} object"
87
+ fail Error, "source file needs to respond to #path, or be a String, a Pathname, or a #{accumulator_class} object"
89
88
  end
90
89
  end
91
90
  end
@@ -1,23 +1,26 @@
1
1
  module ImageProcessing
2
2
  class Processor
3
- def initialize(pipeline)
4
- @pipeline = pipeline
3
+ def self.accumulator(name, klass)
4
+ define_method(name) { @accumulator }
5
+ protected(name)
6
+ const_set(:ACCUMULATOR_CLASS, klass)
5
7
  end
6
8
 
7
- def apply_operation(name, image, *args)
8
- if respond_to?(name)
9
- public_send(name, image, *args)
9
+ def self.apply_operation(accumulator, name, *args, &block)
10
+ if (instance_methods - Object.instance_methods).include?(name)
11
+ instance = new(accumulator)
12
+ instance.public_send(name, *args, &block)
10
13
  else
11
- image.send(name, *args)
14
+ accumulator.send(name, *args, &block)
12
15
  end
13
16
  end
14
17
 
15
- def custom(image, block)
16
- (block && block.call(image)) || image
18
+ def initialize(accumulator = nil)
19
+ @accumulator = accumulator
17
20
  end
18
21
 
19
- private
20
-
21
- attr_reader :pipeline
22
+ def custom(&block)
23
+ (block && block.call(@accumulator)) || @accumulator
24
+ end
22
25
  end
23
26
  end
@@ -1,3 +1,3 @@
1
1
  module ImageProcessing
2
- VERSION = "1.4.0"
2
+ VERSION = "1.5.0"
3
3
  end
@@ -15,41 +15,59 @@ module ImageProcessing
15
15
  end
16
16
 
17
17
  class Processor < ImageProcessing::Processor
18
- IMAGE_CLASS = ::Vips::Image
18
+ accumulator :image, ::Vips::Image
19
+
19
20
  # default sharpening mask that provides a fast and mild sharpen
20
21
  SHARPEN_MASK = ::Vips::Image.new_from_array [[-1, -1, -1],
21
22
  [-1, 32, -1],
22
23
  [-1, -1, -1]], 24
23
24
 
24
- def apply_operation(name, image, *args)
25
- result = super
26
- result.is_a?(::Vips::Image) ? result : image
25
+
26
+ def self.load_image(path_or_image, autorot: true, **options)
27
+ if path_or_image.is_a?(::Vips::Image)
28
+ image = path_or_image
29
+ else
30
+ source_path = path_or_image
31
+ options = Utils.select_valid_loader_options(source_path, options)
32
+
33
+ image = ::Vips::Image.new_from_file(source_path, **options)
34
+ end
35
+
36
+ image = image.autorot if autorot && !options.key?(:autorotate)
37
+ image
27
38
  end
28
39
 
29
- def resize_to_limit(image, width, height, **options)
40
+ def self.save_image(image, destination_path, quality: nil, **options)
41
+ options = options.merge(Q: quality) if quality
42
+ options = Utils.select_valid_saver_options(destination_path, options)
43
+
44
+ image.write_to_file(destination_path, **options)
45
+ end
46
+
47
+ def resize_to_limit(width, height, **options)
30
48
  width, height = default_dimensions(width, height)
31
- thumbnail(image, width, height, size: :down, **options)
49
+ thumbnail(width, height, size: :down, **options)
32
50
  end
33
51
 
34
- def resize_to_fit(image, width, height, **options)
52
+ def resize_to_fit(width, height, **options)
35
53
  width, height = default_dimensions(width, height)
36
- thumbnail(image, width, height, **options)
54
+ thumbnail(width, height, **options)
37
55
  end
38
56
 
39
- def resize_to_fill(image, width, height, **options)
40
- thumbnail(image, width, height, crop: :centre, **options)
57
+ def resize_to_fill(width, height, **options)
58
+ thumbnail(width, height, crop: :centre, **options)
41
59
  end
42
60
 
43
- def resize_and_pad(image, width, height, gravity: "centre", extend: nil, background: nil, alpha: nil, **options)
61
+ def resize_and_pad(width, height, gravity: "centre", extend: nil, background: nil, alpha: nil, **options)
44
62
  embed_options = { extend: extend, background: background }
45
63
  embed_options.reject! { |name, value| value.nil? }
46
64
 
47
- image = thumbnail(image, width, height, **options)
65
+ image = thumbnail(width, height, **options)
48
66
  image = image.add_alpha if alpha && !image.has_alpha?
49
67
  image.gravity(gravity, width, height, **embed_options)
50
68
  end
51
69
 
52
- def rotate(image, degrees, background: nil)
70
+ def rotate(degrees, background: nil)
53
71
  if degrees % 90 == 0
54
72
  image.rot(:"d#{degrees % 360}")
55
73
  else
@@ -60,30 +78,33 @@ module ImageProcessing
60
78
  end
61
79
  end
62
80
 
63
- def load_image(path_or_image, autorot: true, **options)
64
- if path_or_image.is_a?(::Vips::Image)
65
- image = path_or_image
66
- else
67
- source_path = path_or_image
68
- options = select_valid_loader_options(source_path, options)
69
-
70
- image = ::Vips::Image.new_from_file(source_path, **options)
81
+ def composite(other, mode, **options)
82
+ other = [other] unless other.is_a?(Array)
83
+
84
+ other = other.map do |object|
85
+ if object.is_a?(String)
86
+ ::Vips::Image.new_from_file(object)
87
+ elsif object.respond_to?(:to_path)
88
+ ::Vips::Image.new_from_file(object.to_path)
89
+ elsif object.respond_to?(:path)
90
+ ::Vips::Image.new_from_file(object.path)
91
+ else
92
+ object
93
+ end
71
94
  end
72
95
 
73
- image = image.autorot if autorot && !options.key?(:autorotate)
74
- image
96
+ image.composite(other, mode, **options)
75
97
  end
76
98
 
77
- def save_image(image, destination_path, quality: nil, **options)
78
- options = options.merge(Q: quality) if quality
79
- options = select_valid_saver_options(destination_path, options)
80
-
81
- image.write_to_file(destination_path, **options)
82
- end
99
+ # make Vips::Image#set, #set_type, and #set_value chainable
100
+ def set(*args) image.tap { |img| img.set(*args) } end
101
+ def set_type(*args) image.tap { |img| img.set_type(*args) } end
102
+ def set_value(*args) image.tap { |img| img.set_value(*args) } end
83
103
 
84
104
  private
85
105
 
86
- def thumbnail(image, width, height, sharpen: SHARPEN_MASK, **options)
106
+ def thumbnail(width, height, sharpen: SHARPEN_MASK, **options)
107
+ image = self.image
87
108
  image = image.thumbnail_image(width, height: height, **options)
88
109
  image = image.conv(sharpen) if sharpen
89
110
  image
@@ -95,25 +116,29 @@ module ImageProcessing
95
116
  [width || ::Vips::MAX_COORD, height || ::Vips::MAX_COORD]
96
117
  end
97
118
 
98
- def select_valid_loader_options(source_path, options)
99
- loader = ::Vips.vips_foreign_find_load(source_path)
100
- loader ? select_valid_options(loader, options) : options
101
- end
119
+ module Utils
120
+ module_function
102
121
 
103
- def select_valid_saver_options(destination_path, options)
104
- saver = ::Vips.vips_foreign_find_save(destination_path)
105
- saver ? select_valid_options(saver, options) : options
106
- end
122
+ def select_valid_loader_options(source_path, options)
123
+ loader = ::Vips.vips_foreign_find_load(source_path)
124
+ loader ? select_valid_options(loader, options) : options
125
+ end
126
+
127
+ def select_valid_saver_options(destination_path, options)
128
+ saver = ::Vips.vips_foreign_find_save(destination_path)
129
+ saver ? select_valid_options(saver, options) : options
130
+ end
107
131
 
108
- def select_valid_options(operation_name, options)
109
- operation = ::Vips::Operation.new(operation_name)
132
+ def select_valid_options(operation_name, options)
133
+ operation = ::Vips::Operation.new(operation_name)
110
134
 
111
- operation_options = operation.get_construct_args
112
- .select { |name, flags| (flags & ::Vips::ARGUMENT_INPUT) != 0 }
113
- .select { |name, flags| (flags & ::Vips::ARGUMENT_REQUIRED) == 0 }
114
- .map(&:first).map(&:to_sym)
135
+ operation_options = operation.get_construct_args
136
+ .select { |name, flags| (flags & ::Vips::ARGUMENT_INPUT) != 0 }
137
+ .select { |name, flags| (flags & ::Vips::ARGUMENT_REQUIRED) == 0 }
138
+ .map(&:first).map(&:to_sym)
115
139
 
116
- options.select { |name, value| operation_options.include?(name) }
140
+ options.select { |name, value| operation_options.include?(name) }
141
+ end
117
142
  end
118
143
  end
119
144
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: image_processing
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.4.0
4
+ version: 1.5.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Janko Marohnić
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2018-06-15 00:00:00.000000000 Z
11
+ date: 2018-07-09 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: mini_magick