image_processing 1.9.3 → 1.12.2

Sign up to get free protection for your applications and to get access to all the features.
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: []