image_processing 1.10.1 → 1.12.1

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: b5141a7684e311666d6a8a91bf74cd791757df4bafe8b989f42c4785aa7bd2bf
4
- data.tar.gz: 2639145e388bf411cbf03559151c12a5eec8757e96b6bd52ae993533e7559781
3
+ metadata.gz: ec4b4776480756b2721185419de81f228e5ca927b4bd3077fb4c0a5dcafef15f
4
+ data.tar.gz: d40b8822b6a7b2b5e1bad182099f7e3b23b25d0cdd6b61b1afa3154b665e76a6
5
5
  SHA512:
6
- metadata.gz: aa03273ffdb5d53a8cd944921db4e6359fb198e308373a15618c4c824cbe981e41c40544f7b64f1008175de30d2ea60f92cfb346564f1d4166e164d27759c4af
7
- data.tar.gz: febc2324ceccb4b0fca18c708ad3c2423239fa458065cb74790a574e8149acc6285709d356eb99ec2896611803cc536707b47c1b3b3c68b3067377fb881dffb0
6
+ metadata.gz: 77cff245646f56f409d4c1d006d26cee819d11cc9c263b88d86bafb6e480ac691240bfa17542bf9c578891056355d53f63fefddfa1f3eb801e3ddeea20666ae3
7
+ data.tar.gz: bf3f39c4204d7dcbb6382596a4a8850f8c7d22fee94e8ed77a1782a58bd1f370448268bb4e74028e429ee2e7c6156613b1022f5465faf6d30ab01833751c4df9
@@ -1,3 +1,27 @@
1
+ ## 1.12.1 (2020-11-06)
2
+
3
+ * Fix format fallback for files ending with a dot on Ruby 2.7+ (@coding-chimp)
4
+
5
+ ## 1.12.0 (2020-09-20)
6
+
7
+ * Add instrumentation support via `#instrumenter` (@janko)
8
+
9
+ ## 1.11.0 (2020-05-17)
10
+
11
+ * [minimagick] Handle destination format having no file extension (@janko)
12
+
13
+ * [minimagick] Disable sharpening on `#resize_*` operators by default (@flori)
14
+
15
+ * [minimagick] Add `#crop` which accepts `left, top, width, height` arguments (@janko)
16
+
17
+ ## 1.10.3 (2020-01-12)
18
+
19
+ * [vips] Fix auto-rotation not working in certain cases on libvips 8.9.0 (@janko)
20
+
21
+ ## 1.10.2 (2020-01-11)
22
+
23
+ * Fix Ruby 2.7 warnings for separation of positional and keyword arguments (@kamipo, @janko)
24
+
1
25
  ## 1.10.1 (2020-01-07)
2
26
 
3
27
  * [vips] Fix compatibility with ruby-vips 2.0.17+ (@janko)
data/README.md CHANGED
@@ -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"]
@@ -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) }
@@ -37,9 +37,9 @@ module ImageProcessing
37
37
 
38
38
  # Determines the appropriate destination image format.
39
39
  def destination_format
40
- format = File.extname(destination)[1..-1] if destination
40
+ format = determine_format(destination) if destination
41
41
  format ||= self.format
42
- format ||= File.extname(source_path)[1..-1] if source_path
42
+ format ||= determine_format(source_path) if source_path
43
43
 
44
44
  format || DEFAULT_FORMAT
45
45
  end
@@ -93,5 +93,11 @@ module ImageProcessing
93
93
  @source
94
94
  end
95
95
  end
96
+
97
+ def determine_format(file_path)
98
+ extension = File.extname(file_path)
99
+
100
+ extension[1..-1] if extension.size > 1
101
+ end
96
102
  end
97
103
  end
@@ -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.1"
2
+ VERSION = "1.12.1"
3
3
  end
@@ -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
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.1
4
+ version: 1.12.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Janko Marohnić
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-01-07 00:00:00.000000000 Z
11
+ date: 2020-11-06 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: mini_magick
@@ -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: []
@@ -158,8 +159,9 @@ required_rubygems_version: !ruby/object:Gem::Requirement
158
159
  - !ruby/object:Gem::Version
159
160
  version: '0'
160
161
  requirements: []
161
- rubygems_version: 3.1.2
162
+ rubygems_version: 3.1.4
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: []