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 +4 -4
- data/CHANGELOG.md +24 -0
- data/README.md +28 -0
- data/image_processing.gemspec +2 -2
- data/lib/image_processing/builder.rb +18 -2
- data/lib/image_processing/chainable.rb +34 -22
- data/lib/image_processing/mini_magick.rb +13 -4
- data/lib/image_processing/pipeline.rb +8 -2
- data/lib/image_processing/processor.rb +18 -10
- data/lib/image_processing/version.rb +1 -1
- data/lib/image_processing/vips.rb +5 -0
- metadata +7 -5
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: ec4b4776480756b2721185419de81f228e5ca927b4bd3077fb4c0a5dcafef15f
|
4
|
+
data.tar.gz: d40b8822b6a7b2b5e1bad182099f7e3b23b25d0cdd6b61b1afa3154b665e76a6
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 77cff245646f56f409d4c1d006d26cee819d11cc9c263b88d86bafb6e480ac691240bfa17542bf9c578891056355d53f63fefddfa1f3eb801e3ddeea20666ae3
|
7
|
+
data.tar.gz: bf3f39c4204d7dcbb6382596a4a8850f8c7d22fee94e8ed77a1782a58bd1f370448268bb4e74028e429ee2e7c6156613b1022f5465faf6d30ab01833751c4df9
|
data/CHANGELOG.md
CHANGED
@@ -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
|
|
data/image_processing.gemspec
CHANGED
@@ -6,8 +6,8 @@ Gem::Specification.new do |spec|
|
|
6
6
|
|
7
7
|
spec.required_ruby_version = ">= 2.3"
|
8
8
|
|
9
|
-
spec.summary = "
|
10
|
-
spec.description = "
|
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!(**
|
13
|
-
|
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(
|
67
|
-
|
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(
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
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
|
-
|
80
|
+
private
|
76
81
|
|
77
|
-
|
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(
|
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 =
|
40
|
+
format = determine_format(destination) if destination
|
41
41
|
format ||= self.format
|
42
|
-
format ||=
|
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
|
-
#
|
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
|
-
|
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)
|
@@ -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.
|
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-
|
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:
|
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.
|
162
|
+
rubygems_version: 3.1.4
|
162
163
|
signing_key:
|
163
164
|
specification_version: 4
|
164
|
-
summary:
|
165
|
+
summary: High-level wrapper for processing images for the web with ImageMagick or
|
166
|
+
libvips.
|
165
167
|
test_files: []
|