image_processing 1.9.3 → 1.12.2

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 8ab84d40ad0605bafb61684f6f684c95b9bea98ff808791b8ee7316133f3f4c8
4
- data.tar.gz: aa388b9bf19c55e869d4dfce8c4c47a82621c35022ddb77bcf6d940dcc0a9edc
3
+ metadata.gz: 31d06cb35ac94ec0787b21dd2118626f3a3a96685efab0d7eb58d9079b1fbf2c
4
+ data.tar.gz: 8f840074b8fe9511e59f4f005d144e39c9aba4d0699a48c29ffc6cb2513ec645
5
5
  SHA512:
6
- metadata.gz: 74a38f90d9bc088a3919b884d46ff594ece2d9f1b4ae746c94fdd65198915f927245ed3d8a220070259e87bd0f36ac945d6b1378b823535496b02ee7d554cade
7
- data.tar.gz: 2e865256acd306dcbb100687be16e826a6e9381c519a81925e0226684118cc129123124927ac91f5cbeb34e07facb25958789ebaac2acebd7cdb2561623fff9f
6
+ metadata.gz: 1d0fb1d9363b9b85e1895d05849a5c29b2c50b8213bf9e9d6ff8c2e1391a9bf6448a4e319a4cfd747183ec741ac42d6b1b1b9629ee91d97cc4a287ec6615042c
7
+ data.tar.gz: dd31dd0ca012ceb35aa276c5e00efd3e9bd2c40246f9b063ea39280ea13c546373fa641518fb55ebcf0e9d00ad6020be995b8d8636c8a3939bda31c643485cef
data/CHANGELOG.md CHANGED
@@ -1,3 +1,39 @@
1
+ ## 1.12.2 (2022-03-01)
2
+
3
+ * Prevent remote shell execution when using `#apply` with operations coming from user input (@janko)
4
+
5
+ ## 1.12.1 (2020-11-06)
6
+
7
+ * Fix format fallback for files ending with a dot on Ruby 2.7+ (@coding-chimp)
8
+
9
+ ## 1.12.0 (2020-09-20)
10
+
11
+ * Add instrumentation support via `#instrumenter` (@janko)
12
+
13
+ ## 1.11.0 (2020-05-17)
14
+
15
+ * [minimagick] Handle destination format having no file extension (@janko)
16
+
17
+ * [minimagick] Disable sharpening on `#resize_*` operators by default (@flori)
18
+
19
+ * [minimagick] Add `#crop` which accepts `left, top, width, height` arguments (@janko)
20
+
21
+ ## 1.10.3 (2020-01-12)
22
+
23
+ * [vips] Fix auto-rotation not working in certain cases on libvips 8.9.0 (@janko)
24
+
25
+ ## 1.10.2 (2020-01-11)
26
+
27
+ * Fix Ruby 2.7 warnings for separation of positional and keyword arguments (@kamipo, @janko)
28
+
29
+ ## 1.10.1 (2020-01-07)
30
+
31
+ * [vips] Fix compatibility with ruby-vips 2.0.17+ (@janko)
32
+
33
+ ## 1.10.0 (2019-12-18)
34
+
35
+ * [minimagick] Add `:loader` option for explicitly setting input file type (@janko)
36
+
1
37
  ## 1.9.3 (2019-08-11)
2
38
 
3
39
  * [vips] Use integer precision when sharpening for better quality (@metaskills)
data/README.md CHANGED
@@ -29,10 +29,18 @@ how to resize and process images.
29
29
 
30
30
  1. Install ImageMagick and/or libvips:
31
31
 
32
- ```sh
32
+ In a Mac terminal:
33
+
34
+ ```sh
33
35
  $ brew install imagemagick vips
34
36
  ```
35
37
 
38
+ In a debian/ubuntu terminal:
39
+
40
+ ```sh
41
+ $ sudo apt install imagemagick libvips
42
+ ```
43
+
36
44
  2. Add the gem to your Gemfile:
37
45
 
38
46
  ```rb
@@ -42,9 +50,9 @@ how to resize and process images.
42
50
 
43
51
  ## Usage
44
52
 
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:
53
+ Processing is performed through **[`ImageProcessing::Vips`]** or
54
+ **[`ImageProcessing::MiniMagick`]** modules. Both modules share the same
55
+ chainable API for defining the processing pipeline:
48
56
 
49
57
  ```rb
50
58
  require "image_processing/mini_magick"
@@ -148,15 +156,50 @@ You can continue reading the API documentation for specific modules:
148
156
  See the **[wiki]** for additional "How To" guides for common scenarios. The wiki
149
157
  is publicly editable, so you're encouraged to add your own guides.
150
158
 
159
+ ## Instrumentation
160
+
161
+ You can register an `#instrumenter` block for a given pipeline, which will wrap
162
+ the pipeline execution, allowing you to record performance metrics.
163
+
164
+ ```rb
165
+ pipeline = ImageProcessing::Vips.instrumenter do |**options, &processing|
166
+ options[:source] #=> #<File:...>
167
+ options[:loader] #=> { fail: true }
168
+ options[:saver] #=> { quality: 85 }
169
+ options[:format] #=> "png"
170
+ options[:operations] #=> [[:resize_to_limit, 500, 500], [:flip, [:horizontal]]]
171
+ options[:processor] #=> ImageProcessing::Vips::Processor
172
+
173
+ ActiveSupport::Notifications.instrument("process.image_processing", **options) do
174
+ processing.call # calls the pipeline
175
+ end
176
+ end
177
+
178
+ pipeline
179
+ .source(image)
180
+ .loader(fail: true)
181
+ .saver(quality: 85)
182
+ .convert("png")
183
+ .resize_to_limit(500, 500)
184
+ .flip(:horizontal)
185
+ .call # calls instrumenter
186
+ ```
151
187
 
152
188
  ## Contributing
153
189
 
154
190
  Our test suite requires both `imagemagick` and `libvips` libraries to be installed.
155
191
 
192
+ In a Mac terminal:
193
+
156
194
  ```
157
195
  $ brew install imagemagick vips
158
196
  ```
159
197
 
198
+ In a debian/ubuntu terminal:
199
+ ```shell
200
+ sudo apt install imagemagick libvips
201
+ ```
202
+
160
203
  Afterwards you can run tests with
161
204
 
162
205
  ```
@@ -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,11 +17,11 @@ 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"
24
24
  spec.add_development_dependency "minitest-hooks", ">= 1.4.2"
25
25
  spec.add_development_dependency "minispec-metadata"
26
- spec.add_development_dependency "phashion" unless RUBY_ENGINE == "jruby"
26
+ spec.add_development_dependency "dhash-vips"
27
27
  end
@@ -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)
@@ -29,24 +34,17 @@ module ImageProcessing
29
34
  def apply(operations)
30
35
  operations.inject(self) do |builder, (name, argument)|
31
36
  if argument == true || argument == nil
32
- builder.send(name)
37
+ builder.public_send(name)
33
38
  elsif argument.is_a?(Array)
34
- builder.send(name, *argument)
39
+ builder.public_send(name, *argument)
40
+ elsif argument.is_a?(Hash)
41
+ builder.public_send(name, **argument)
35
42
  else
36
- builder.send(name, argument)
43
+ builder.public_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]]
@@ -56,26 +54,42 @@ module ImageProcessing
56
54
  # the source file and destination.
57
55
  def call(file = nil, destination: nil, **call_options)
58
56
  options = {}
59
- options = options.merge(source: file) if file
60
- options = options.merge(destination: destination) if destination
57
+ options[:source] = file if file
58
+ options[:destination] = destination if destination
61
59
 
62
- branch(options).call!(**call_options)
60
+ branch(**options).call!(**call_options)
63
61
  end
64
62
 
65
63
  # 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
64
+ def branch(**new_options)
65
+ if self.is_a?(Builder)
66
+ options = self.options
67
+ else
68
+ options = DEFAULT_OPTIONS.merge(processor: self::Processor)
69
+ end
68
70
 
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)
71
+ options = options.merge(new_options) do |key, old_value, new_value|
72
+ case key
73
+ when :loader, :saver then old_value.merge(new_value)
74
+ when :operations then old_value + new_value
75
+ else new_value
76
+ end
77
+ end
78
+
79
+ Builder.new(options.freeze)
80
+ end
74
81
 
75
- options.freeze
82
+ private
76
83
 
77
- Builder.new(options)
84
+ # Assume that any unknown method names an operation supported by the
85
+ # processor. Add a bang ("!") if you want processing to be performed.
86
+ def method_missing(name, *args, &block)
87
+ return super if name.to_s.end_with?("?")
88
+ return send(name.to_s.chomp("!"), *args, &block).call if name.to_s.end_with?("!")
89
+
90
+ operation(name, *args, &block)
78
91
  end
92
+ ruby2_keywords(:method_missing) if respond_to?(:ruby2_keywords, true)
79
93
 
80
94
  # Empty options which the builder starts with.
81
95
  DEFAULT_OPTIONS = {
@@ -25,20 +25,21 @@ module ImageProcessing
25
25
  # Initializes the image on disk into a MiniMagick::Tool object. Accepts
26
26
  # additional options related to loading the image (e.g. geometry).
27
27
  # Additionally auto-orients the image to be upright.
28
- def self.load_image(path_or_magick, page: nil, geometry: nil, auto_orient: true, **options)
28
+ def self.load_image(path_or_magick, loader: nil, page: nil, geometry: nil, auto_orient: true, **options)
29
29
  if path_or_magick.is_a?(::MiniMagick::Tool)
30
30
  magick = path_or_magick
31
31
  else
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
- input_path = source_path
38
- input_path += "[#{page}]" if page
39
- input_path += "[#{geometry}]" if geometry
37
+ input = source_path
38
+ input = "#{loader}:#{input}" if loader
39
+ input += "[#{page}]" if page
40
+ input += "[#{geometry}]" if geometry
40
41
 
41
- magick << input_path
42
+ magick << input
42
43
  end
43
44
 
44
45
  magick.auto_orient if auto_orient
@@ -49,7 +50,7 @@ module ImageProcessing
49
50
  # the result to disk. Accepts additional options related to saving the
50
51
  # image (e.g. quality).
51
52
  def self.save_image(magick, destination_path, allow_splitting: false, **options)
52
- Utils.apply_options(magick, options)
53
+ Utils.apply_options(magick, **options)
53
54
 
54
55
  magick << destination_path
55
56
  magick.call
@@ -85,6 +86,15 @@ module ImageProcessing
85
86
  magick.extent "#{width}x#{height}"
86
87
  end
87
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
+
88
98
  # Rotates the image by an arbitrary angle. For angles that are not
89
99
  # multiple of 90 degrees an optional background color can be specified to
90
100
  # fill in the gaps.
@@ -160,7 +170,7 @@ module ImageProcessing
160
170
 
161
171
  # Resizes the image using the specified geometry, and sharpens the
162
172
  # resulting thumbnail.
163
- def thumbnail(geometry, sharpen: {})
173
+ def thumbnail(geometry, sharpen: nil)
164
174
  magick.resize(geometry)
165
175
 
166
176
  if sharpen
@@ -191,7 +201,7 @@ module ImageProcessing
191
201
  # format, ImageMagick will create multiple images, one for each layer.
192
202
  # We want to warn the user that this is probably not what they wanted.
193
203
  def disallow_split_layers!(destination_path)
194
- layers = Dir[destination_path.sub(/\.\w+$/, '-*\0')]
204
+ layers = Dir[destination_path.sub(/(\.\w+)?$/, '-*\0')]
195
205
 
196
206
  if layers.any?
197
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.9.3"
2
+ VERSION = "1.12.2"
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.9.3
4
+ version: 1.12.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Janko Marohnić
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2019-08-11 00:00:00.000000000 Z
11
+ date: 2022-03-01 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'
@@ -107,7 +107,7 @@ dependencies:
107
107
  - !ruby/object:Gem::Version
108
108
  version: '0'
109
109
  - !ruby/object:Gem::Dependency
110
- name: phashion
110
+ name: dhash-vips
111
111
  requirement: !ruby/object:Gem::Requirement
112
112
  requirements:
113
113
  - - ">="
@@ -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: []
@@ -143,7 +144,7 @@ homepage: https://github.com/janko/image_processing
143
144
  licenses:
144
145
  - MIT
145
146
  metadata: {}
146
- post_install_message:
147
+ post_install_message:
147
148
  rdoc_options: []
148
149
  require_paths:
149
150
  - lib
@@ -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.0.3
162
- signing_key:
162
+ rubygems_version: 3.3.3
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: []