image_processing 1.4.0 → 1.5.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.

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