image_processing 1.10.0 → 1.12.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: e15f100c14c6b60a0107e1d3b2f6ff41e2890f9ecd1fb3f73ae4838249650746
4
- data.tar.gz: 2a8c07a6891465aebb7d172354780a5cba8d75933c4c7354d7b68acf8c6b321f
3
+ metadata.gz: 0e86e76046f3b8e0b9fd36a71d52b158a923c75f33848692de1e199ecb17366f
4
+ data.tar.gz: 7267f19b5cb3224a61e04be0a0d3b3f17d353750a1a522a11f2912b9ba49b809
5
5
  SHA512:
6
- metadata.gz: 21f23471ac04698370c6b3a77a82d8a4be70a754ff4f5440ae91ee03a9d1e52c03b5b0bf0a54e7bd523fdb174ecc805158dd0a013ad88bfa1c3945cc2b973c81
7
- data.tar.gz: f90f0ae07f460358571b929ef2f831fdb640bbbcfda7c7ba9f6416e0d24ea303c8431e0cd193137d4228a626c0f74a7f09839f732db0289c092459e7c4c28ade
6
+ metadata.gz: 473958a39a8e760cdb9d352399190f1cdea0dcd915ad729ec740fb7f424dc05041c43fe1085439c0f54763c388b83c2b7dcf32f66cc15caa9dbc0f21c325a166
7
+ data.tar.gz: ac3cc41432e9dc786035f77c721eb1407c84c6307fccc9d8f01f42ca909618229134411f503eeeb770809f620804468750414e6dc8900c442a46d08d3d02e660
@@ -1,3 +1,27 @@
1
+ ## 1.12.0 (2020-09-20)
2
+
3
+ * Add instrumentation support via `#instrumenter` (@janko)
4
+
5
+ ## 1.11.0 (2020-05-17)
6
+
7
+ * [minimagick] Handle destination format having no file extension (@janko)
8
+
9
+ * [minimagick] Disable sharpening on `#resize_*` operators by default (@flori)
10
+
11
+ * [minimagick] Add `#crop` which accepts `left, top, width, height` arguments (@janko)
12
+
13
+ ## 1.10.3 (2020-01-12)
14
+
15
+ * [vips] Fix auto-rotation not working in certain cases on libvips 8.9.0 (@janko)
16
+
17
+ ## 1.10.2 (2020-01-11)
18
+
19
+ * Fix Ruby 2.7 warnings for separation of positional and keyword arguments (@kamipo, @janko)
20
+
21
+ ## 1.10.1 (2020-01-07)
22
+
23
+ * [vips] Fix compatibility with ruby-vips 2.0.17+ (@janko)
24
+
1
25
  ## 1.10.0 (2019-12-18)
2
26
 
3
27
  * [minimagick] Add `:loader` option for explicitly setting input file type (@janko)
data/README.md CHANGED
@@ -42,9 +42,9 @@ how to resize and process images.
42
42
 
43
43
  ## Usage
44
44
 
45
- Processing is performed through [`ImageProcessing::Vips`] or
46
- [`ImageProcessing::MiniMagick`] modules. Both modules share the same chainable
47
- API for defining the processing pipeline:
45
+ Processing is performed through **[`ImageProcessing::Vips`]** or
46
+ **[`ImageProcessing::MiniMagick`]** modules. Both modules share the same
47
+ chainable API for defining the processing pipeline:
48
48
 
49
49
  ```rb
50
50
  require "image_processing/mini_magick"
@@ -148,6 +148,34 @@ You can continue reading the API documentation for specific modules:
148
148
  See the **[wiki]** for additional "How To" guides for common scenarios. The wiki
149
149
  is publicly editable, so you're encouraged to add your own guides.
150
150
 
151
+ ## Instrumentation
152
+
153
+ You can register an `#instrumenter` block for a given pipeline, which will wrap
154
+ the pipeline execution, allowing you to record performance metrics.
155
+
156
+ ```rb
157
+ pipeline = ImageProcessing::Vips.instrumenter do |**options, &processing|
158
+ options[:source] #=> #<File:...>
159
+ options[:loader] #=> { fail: true }
160
+ options[:saver] #=> { quality: 85 }
161
+ options[:format] #=> "png"
162
+ options[:operations] #=> [[:resize_to_limit, 500, 500], [:flip, [:horizontal]]]
163
+ options[:processor] #=> ImageProcessing::Vips::Processor
164
+
165
+ ActiveSupport::Notifications.instrument("process.image_processing", **options) do
166
+ processing.call # calls the pipeline
167
+ end
168
+ end
169
+
170
+ pipeline
171
+ .source(image)
172
+ .loader(fail: true)
173
+ .saver(quality: 85)
174
+ .convert("png")
175
+ .resize_to_limit(500, 500)
176
+ .flip(:horizontal)
177
+ .call # calls instrumenter
178
+ ```
151
179
 
152
180
  ## Contributing
153
181
 
@@ -6,8 +6,8 @@ Gem::Specification.new do |spec|
6
6
 
7
7
  spec.required_ruby_version = ">= 2.3"
8
8
 
9
- spec.summary = "Set of higher-level helper methods for image processing."
10
- spec.description = "Set of higher-level helper methods for image processing."
9
+ spec.summary = "High-level wrapper for processing images for the web with ImageMagick or libvips."
10
+ spec.description = "High-level wrapper for processing images for the web with ImageMagick or libvips."
11
11
  spec.homepage = "https://github.com/janko/image_processing"
12
12
  spec.authors = ["Janko Marohnić"]
13
13
  spec.email = ["janko.marohnic@gmail.com"]
@@ -17,7 +17,7 @@ Gem::Specification.new do |spec|
17
17
  spec.require_paths = ["lib"]
18
18
 
19
19
  spec.add_dependency "mini_magick", ">= 4.9.5", "< 5"
20
- spec.add_dependency "ruby-vips", ">= 2.0.13", "< 3"
20
+ spec.add_dependency "ruby-vips", ">= 2.0.17", "< 3"
21
21
 
22
22
  spec.add_development_dependency "rake"
23
23
  spec.add_development_dependency "minitest", "~> 5.8"
@@ -9,8 +9,24 @@ module ImageProcessing
9
9
  end
10
10
 
11
11
  # Calls the pipeline to perform the processing from built options.
12
- def call!(**options)
13
- Pipeline.new(self.options).call(**options)
12
+ def call!(**call_options)
13
+ instrument do
14
+ Pipeline.new(pipeline_options).call(**call_options)
15
+ end
16
+ end
17
+
18
+ private
19
+
20
+ def instrument
21
+ return yield unless options[:instrumenter]
22
+
23
+ result = nil
24
+ options[:instrumenter].call(**pipeline_options) { result = yield }
25
+ result
26
+ end
27
+
28
+ def pipeline_options
29
+ options.reject { |key, _| key == :instrumenter }
14
30
  end
15
31
  end
16
32
  end
@@ -21,6 +21,11 @@ module ImageProcessing
21
21
  branch saver: options
22
22
  end
23
23
 
24
+ # Register instrumentation block that will be called around the pipeline.
25
+ def instrumenter(&block)
26
+ branch instrumenter: block
27
+ end
28
+
24
29
  # Add multiple operations as a hash or an array.
25
30
  #
26
31
  # .apply(resize_to_limit: [400, 400], strip: true)
@@ -32,21 +37,14 @@ module ImageProcessing
32
37
  builder.send(name)
33
38
  elsif argument.is_a?(Array)
34
39
  builder.send(name, *argument)
40
+ elsif argument.is_a?(Hash)
41
+ builder.send(name, **argument)
35
42
  else
36
43
  builder.send(name, argument)
37
44
  end
38
45
  end
39
46
  end
40
47
 
41
- # Assume that any unknown method names an operation supported by the
42
- # processor. Add a bang ("!") if you want processing to be performed.
43
- def method_missing(name, *args, &block)
44
- return super if name.to_s.end_with?("?")
45
- return send(name.to_s.chomp("!"), *args, &block).call if name.to_s.end_with?("!")
46
-
47
- operation(name, *args, &block)
48
- end
49
-
50
48
  # Add an operation defined by the processor.
51
49
  def operation(name, *args, &block)
52
50
  branch operations: [[name, args, *block]]
@@ -55,27 +53,41 @@ module ImageProcessing
55
53
  # Call the defined processing and get the result. Allows specifying
56
54
  # the source file and destination.
57
55
  def call(file = nil, destination: nil, **call_options)
58
- options = {}
59
- options = options.merge(source: file) if file
60
- options = options.merge(destination: destination) if destination
56
+ options = { source: file, destination: destination }.compact
61
57
 
62
- branch(options).call!(**call_options)
58
+ branch(**options).call!(**call_options)
63
59
  end
64
60
 
65
61
  # Creates a new builder object, merging current options with new options.
66
- def branch(loader: nil, saver: nil, operations: nil, **other_options)
67
- options = respond_to?(:options) ? self.options : DEFAULT_OPTIONS
62
+ def branch(**new_options)
63
+ if self.is_a?(Builder)
64
+ options = self.options
65
+ else
66
+ options = DEFAULT_OPTIONS.merge(processor: self::Processor)
67
+ end
68
68
 
69
- options = options.merge(loader: options[:loader].merge(loader)) if loader
70
- options = options.merge(saver: options[:saver].merge(saver)) if saver
71
- options = options.merge(operations: options[:operations] + operations) if operations
72
- options = options.merge(processor: self::Processor) unless self.is_a?(Builder)
73
- options = options.merge(other_options)
69
+ options = options.merge(new_options) do |key, old_value, new_value|
70
+ case key
71
+ when :loader, :saver then old_value.merge(new_value)
72
+ when :operations then old_value + new_value
73
+ else new_value
74
+ end
75
+ end
76
+
77
+ Builder.new(options.freeze)
78
+ end
74
79
 
75
- options.freeze
80
+ private
76
81
 
77
- Builder.new(options)
82
+ # Assume that any unknown method names an operation supported by the
83
+ # processor. Add a bang ("!") if you want processing to be performed.
84
+ def method_missing(name, *args, &block)
85
+ return super if name.to_s.end_with?("?")
86
+ return send(name.to_s.chomp("!"), *args, &block).call if name.to_s.end_with?("!")
87
+
88
+ operation(name, *args, &block)
78
89
  end
90
+ ruby2_keywords(:method_missing) if respond_to?(:ruby2_keywords, true)
79
91
 
80
92
  # Empty options which the builder starts with.
81
93
  DEFAULT_OPTIONS = {
@@ -32,7 +32,7 @@ module ImageProcessing
32
32
  source_path = path_or_magick
33
33
  magick = ::MiniMagick::Tool::Convert.new
34
34
 
35
- Utils.apply_options(magick, options)
35
+ Utils.apply_options(magick, **options)
36
36
 
37
37
  input = source_path
38
38
  input = "#{loader}:#{input}" if loader
@@ -50,7 +50,7 @@ module ImageProcessing
50
50
  # the result to disk. Accepts additional options related to saving the
51
51
  # image (e.g. quality).
52
52
  def self.save_image(magick, destination_path, allow_splitting: false, **options)
53
- Utils.apply_options(magick, options)
53
+ Utils.apply_options(magick, **options)
54
54
 
55
55
  magick << destination_path
56
56
  magick.call
@@ -86,6 +86,15 @@ module ImageProcessing
86
86
  magick.extent "#{width}x#{height}"
87
87
  end
88
88
 
89
+ # Crops the image with the specified crop points.
90
+ def crop(*args)
91
+ case args.count
92
+ when 1 then magick.crop(*args)
93
+ when 4 then magick.crop("#{args[2]}x#{args[3]}+#{args[0]}+#{args[1]}")
94
+ else fail ArgumentError, "wrong number of arguments (expected 1 or 4, got #{args.count})"
95
+ end
96
+ end
97
+
89
98
  # Rotates the image by an arbitrary angle. For angles that are not
90
99
  # multiple of 90 degrees an optional background color can be specified to
91
100
  # fill in the gaps.
@@ -161,7 +170,7 @@ module ImageProcessing
161
170
 
162
171
  # Resizes the image using the specified geometry, and sharpens the
163
172
  # resulting thumbnail.
164
- def thumbnail(geometry, sharpen: {})
173
+ def thumbnail(geometry, sharpen: nil)
165
174
  magick.resize(geometry)
166
175
 
167
176
  if sharpen
@@ -192,7 +201,7 @@ module ImageProcessing
192
201
  # format, ImageMagick will create multiple images, one for each layer.
193
202
  # We want to warn the user that this is probably not what they wanted.
194
203
  def disallow_split_layers!(destination_path)
195
- layers = Dir[destination_path.sub(/\.\w+$/, '-*\0')]
204
+ layers = Dir[destination_path.sub(/(\.\w+)?$/, '-*\0')]
196
205
 
197
206
  if layers.any?
198
207
  layers.each { |path| File.delete(path) }
@@ -34,19 +34,12 @@ module ImageProcessing
34
34
  const_set(:ACCUMULATOR_CLASS, klass)
35
35
  end
36
36
 
37
- # Calls the operation to perform the processing. If the operation is
38
- # defined on the processor (macro), calls it. Otherwise calls the
39
- # operation directly on the accumulator object. This provides a common
40
- # umbrella above defined macros and direct operations.
37
+ # Delegates to #apply_operation.
41
38
  def self.apply_operation(accumulator, (name, args, block))
42
- if method_defined?(name)
43
- instance = new(accumulator)
44
- instance.public_send(name, *args, &block)
45
- else
46
- accumulator.send(name, *args, &block)
47
- end
39
+ new(accumulator).apply_operation(name, *args, &block)
48
40
  end
49
41
 
42
+ # Whether the processor supports resizing the image upon loading.
50
43
  def self.supports_resize_on_load?
51
44
  false
52
45
  end
@@ -55,6 +48,21 @@ module ImageProcessing
55
48
  @accumulator = accumulator
56
49
  end
57
50
 
51
+ # Calls the operation to perform the processing. If the operation is
52
+ # defined on the processor (macro), calls the method. Otherwise calls the
53
+ # operation directly on the accumulator object. This provides a common
54
+ # umbrella above defined macros and direct operations.
55
+ def apply_operation(name, *args, &block)
56
+ receiver = respond_to?(name) ? self : @accumulator
57
+
58
+ if args.last.is_a?(Hash)
59
+ kwargs = args.pop
60
+ receiver.public_send(name, *args, **kwargs, &block)
61
+ else
62
+ receiver.public_send(name, *args, &block)
63
+ end
64
+ end
65
+
58
66
  # Calls the given block with the accumulator object. Useful for when you
59
67
  # want to access the accumulator object directly.
60
68
  def custom(&block)
@@ -1,3 +1,3 @@
1
1
  module ImageProcessing
2
- VERSION = "1.10.0"
2
+ VERSION = "1.12.0"
3
3
  end
@@ -54,7 +54,7 @@ module ImageProcessing
54
54
  # pipeline defined in the Vips::Image object. Accepts additional
55
55
  # saver-specific options (e.g. quality).
56
56
  def self.save_image(image, path, saver: nil, quality: nil, **options)
57
- options = options.merge(Q: quality) if quality
57
+ options[:Q] = quality if quality
58
58
 
59
59
  if saver
60
60
  image.public_send(:"#{saver}save", path, **options)
@@ -138,8 +138,13 @@ module ImageProcessing
138
138
  # resize on load
139
139
  image = ::Vips::Image.thumbnail(self.image, width, height: height, **options)
140
140
  else
141
+ # we're already calling Image#autorot when loading the image
142
+ no_rotate = ::Vips.at_least_libvips?(8, 8) ? { no_rotate: true } : { auto_rotate: false }
143
+ options = no_rotate.merge(options)
144
+
141
145
  image = self.image.thumbnail_image(width, height: height, **options)
142
146
  end
147
+
143
148
  image = image.conv(sharpen, precision: :integer) if sharpen
144
149
  image
145
150
  end
@@ -187,15 +192,11 @@ module ImageProcessing
187
192
  # output image format. Each of these loaders and savers accept slightly
188
193
  # different options, so to allow the user to be able to specify options
189
194
  # for a specific loader/saver and have it ignored for other
190
- # loaders/savers, we do a little bit of introspection and filter out
191
- # options that don't exist for a particular loader or saver.
195
+ # loaders/savers, we do some introspection and filter out options that
196
+ # don't exist for a particular loader or saver.
192
197
  def select_valid_options(operation_name, options)
193
- operation = ::Vips::Operation.new(operation_name)
194
-
195
- operation_options = operation.get_construct_args
196
- .select { |name, flags| (flags & ::Vips::ARGUMENT_INPUT) != 0 }
197
- .select { |name, flags| (flags & ::Vips::ARGUMENT_REQUIRED) == 0 }
198
- .map(&:first).map(&:to_sym)
198
+ introspect = ::Vips::Introspect.get(operation_name)
199
+ operation_options = introspect.optional_input.keys.map(&:to_sym)
199
200
 
200
201
  options.select { |name, value| operation_options.include?(name) }
201
202
  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.10.0
4
+ version: 1.12.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: 2019-12-18 00:00:00.000000000 Z
11
+ date: 2020-09-20 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: mini_magick
@@ -36,7 +36,7 @@ dependencies:
36
36
  requirements:
37
37
  - - ">="
38
38
  - !ruby/object:Gem::Version
39
- version: 2.0.13
39
+ version: 2.0.17
40
40
  - - "<"
41
41
  - !ruby/object:Gem::Version
42
42
  version: '3'
@@ -46,7 +46,7 @@ dependencies:
46
46
  requirements:
47
47
  - - ">="
48
48
  - !ruby/object:Gem::Version
49
- version: 2.0.13
49
+ version: 2.0.17
50
50
  - - "<"
51
51
  - !ruby/object:Gem::Version
52
52
  version: '3'
@@ -120,7 +120,8 @@ dependencies:
120
120
  - - ">="
121
121
  - !ruby/object:Gem::Version
122
122
  version: '0'
123
- description: Set of higher-level helper methods for image processing.
123
+ description: High-level wrapper for processing images for the web with ImageMagick
124
+ or libvips.
124
125
  email:
125
126
  - janko.marohnic@gmail.com
126
127
  executables: []
@@ -161,5 +162,6 @@ requirements: []
161
162
  rubygems_version: 3.1.1
162
163
  signing_key:
163
164
  specification_version: 4
164
- summary: Set of higher-level helper methods for image processing.
165
+ summary: High-level wrapper for processing images for the web with ImageMagick or
166
+ libvips.
165
167
  test_files: []