image_processing 1.10.1 → 1.12.1

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: 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: []