image_processing 1.2.0 → 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 +4 -4
- data/CHANGELOG.md +143 -47
- data/README.md +62 -16
- data/image_processing.gemspec +8 -8
- data/lib/image_processing/builder.rb +19 -2
- data/lib/image_processing/chainable.rb +59 -32
- data/lib/image_processing/mini_magick.rb +174 -74
- data/lib/image_processing/pipeline.rb +39 -28
- data/lib/image_processing/processor.rb +60 -11
- data/lib/image_processing/version.rb +1 -1
- data/lib/image_processing/vips.rb +144 -60
- metadata +25 -18
@@ -7,6 +7,7 @@ module ImageProcessing
|
|
7
7
|
module Vips
|
8
8
|
extend Chainable
|
9
9
|
|
10
|
+
# Returns whether the given image file is processable.
|
10
11
|
def self.valid_image?(file)
|
11
12
|
::Vips::Image.new_from_file(file.path, access: :sequential).avg
|
12
13
|
true
|
@@ -15,107 +16,190 @@ module ImageProcessing
|
|
15
16
|
end
|
16
17
|
|
17
18
|
class Processor < ImageProcessing::Processor
|
18
|
-
|
19
|
-
|
19
|
+
accumulator :image, ::Vips::Image
|
20
|
+
|
21
|
+
# Default sharpening mask that provides a fast and mild sharpen.
|
20
22
|
SHARPEN_MASK = ::Vips::Image.new_from_array [[-1, -1, -1],
|
21
23
|
[-1, 32, -1],
|
22
24
|
[-1, -1, -1]], 24
|
23
25
|
|
24
|
-
|
25
|
-
|
26
|
-
|
26
|
+
|
27
|
+
# Loads the image on disk into a Vips::Image object. Accepts additional
|
28
|
+
# loader-specific options (e.g. interlacing). Afterwards auto-rotates the
|
29
|
+
# image to be upright.
|
30
|
+
def self.load_image(path_or_image, loader: nil, autorot: true, **options)
|
31
|
+
if path_or_image.is_a?(::Vips::Image)
|
32
|
+
image = path_or_image
|
33
|
+
else
|
34
|
+
path = path_or_image
|
35
|
+
|
36
|
+
if loader
|
37
|
+
image = ::Vips::Image.public_send(:"#{loader}load", path, **options)
|
38
|
+
else
|
39
|
+
options = Utils.select_valid_loader_options(path, options)
|
40
|
+
image = ::Vips::Image.new_from_file(path, **options)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
image = image.autorot if autorot && !options.key?(:autorotate)
|
45
|
+
image
|
46
|
+
end
|
47
|
+
|
48
|
+
# See #thumbnail.
|
49
|
+
def self.supports_resize_on_load?
|
50
|
+
true
|
51
|
+
end
|
52
|
+
|
53
|
+
# Writes the Vips::Image object to disk. This starts the processing
|
54
|
+
# pipeline defined in the Vips::Image object. Accepts additional
|
55
|
+
# saver-specific options (e.g. quality).
|
56
|
+
def self.save_image(image, path, saver: nil, quality: nil, **options)
|
57
|
+
options[:Q] = quality if quality
|
58
|
+
|
59
|
+
if saver
|
60
|
+
image.public_send(:"#{saver}save", path, **options)
|
61
|
+
else
|
62
|
+
options = Utils.select_valid_saver_options(path, options)
|
63
|
+
image.write_to_file(path, **options)
|
64
|
+
end
|
27
65
|
end
|
28
66
|
|
29
|
-
|
67
|
+
# Resizes the image to not be larger than the specified dimensions.
|
68
|
+
def resize_to_limit(width, height, **options)
|
30
69
|
width, height = default_dimensions(width, height)
|
31
|
-
thumbnail(
|
70
|
+
thumbnail(width, height, size: :down, **options)
|
32
71
|
end
|
33
72
|
|
34
|
-
|
73
|
+
# Resizes the image to fit within the specified dimensions.
|
74
|
+
def resize_to_fit(width, height, **options)
|
35
75
|
width, height = default_dimensions(width, height)
|
36
|
-
thumbnail(
|
76
|
+
thumbnail(width, height, **options)
|
37
77
|
end
|
38
78
|
|
39
|
-
|
40
|
-
|
79
|
+
# Resizes the image to fill the specified dimensions, applying any
|
80
|
+
# necessary cropping.
|
81
|
+
def resize_to_fill(width, height, **options)
|
82
|
+
thumbnail(width, height, crop: :centre, **options)
|
41
83
|
end
|
42
84
|
|
43
|
-
|
44
|
-
|
45
|
-
|
85
|
+
# Resizes the image to fit within the specified dimensions and fills
|
86
|
+
# the remaining area with the specified background color.
|
87
|
+
def resize_and_pad(width, height, gravity: "centre", extend: nil, background: nil, alpha: nil, **options)
|
88
|
+
image = thumbnail(width, height, **options)
|
89
|
+
image = image.add_alpha if alpha && !image.has_alpha?
|
90
|
+
image.gravity(gravity, width, height, extend: extend, background: background)
|
91
|
+
end
|
46
92
|
|
47
|
-
|
48
|
-
|
49
|
-
image.
|
93
|
+
# Rotates the image by an arbitrary angle.
|
94
|
+
def rotate(degrees, **options)
|
95
|
+
image.similarity(angle: degrees, **options)
|
50
96
|
end
|
51
97
|
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
98
|
+
# Overlays the specified image over the current one. Supports specifying
|
99
|
+
# composite mode, direction or offset of the overlay image.
|
100
|
+
def composite(overlay, _mode = nil, mode: "over", gravity: "north-west", offset: nil, **options)
|
101
|
+
# if the mode argument is given, call the original Vips::Image#composite
|
102
|
+
if _mode
|
103
|
+
overlay = [overlay] unless overlay.is_a?(Array)
|
104
|
+
overlay = overlay.map { |object| convert_to_image(object, "overlay") }
|
58
105
|
|
59
|
-
image
|
106
|
+
return image.composite(overlay, _mode, **options)
|
60
107
|
end
|
61
108
|
|
62
|
-
|
63
|
-
|
64
|
-
|
109
|
+
overlay = convert_to_image(overlay, "overlay")
|
110
|
+
# add alpha channel so that #gravity can use a transparent background
|
111
|
+
overlay = overlay.add_alpha unless overlay.has_alpha?
|
112
|
+
|
113
|
+
# apply offset with correct gravity and make remainder transparent
|
114
|
+
if offset
|
115
|
+
opposite_gravity = gravity.to_s.gsub(/\w+/, "north"=>"south", "south"=>"north", "east"=>"west", "west"=>"east")
|
116
|
+
overlay = overlay.gravity(opposite_gravity, overlay.width + offset.first, overlay.height + offset.last)
|
117
|
+
end
|
65
118
|
|
66
|
-
|
67
|
-
|
68
|
-
options = select_valid_saver_options(destination_path, options)
|
119
|
+
# create image-sized transparent background and apply specified gravity
|
120
|
+
overlay = overlay.gravity(gravity, image.width, image.height)
|
69
121
|
|
70
|
-
|
122
|
+
# apply the composition
|
123
|
+
image.composite(overlay, mode, **options)
|
71
124
|
end
|
72
125
|
|
126
|
+
# make metadata setter methods chainable
|
127
|
+
def set(*args) image.tap { |img| img.set(*args) } end
|
128
|
+
def set_type(*args) image.tap { |img| img.set_type(*args) } end
|
129
|
+
def set_value(*args) image.tap { |img| img.set_value(*args) } end
|
130
|
+
def remove(*args) image.tap { |img| img.remove(*args) } end
|
131
|
+
|
73
132
|
private
|
74
133
|
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
image
|
79
|
-
|
134
|
+
# Resizes the image according to the specified parameters, and sharpens
|
135
|
+
# the resulting thumbnail.
|
136
|
+
def thumbnail(width, height, sharpen: SHARPEN_MASK, **options)
|
137
|
+
if self.image.is_a?(String) # path
|
138
|
+
# resize on load
|
139
|
+
image = ::Vips::Image.thumbnail(self.image, width, height: height, **options)
|
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)
|
80
144
|
|
81
|
-
|
82
|
-
|
83
|
-
max_alpha = (image.interpretation == :grey16 || image.interpretation == :rgb16) ? 65535 : 255
|
84
|
-
image.bandjoin(max_alpha)
|
85
|
-
end
|
145
|
+
image = self.image.thumbnail_image(width, height: height, **options)
|
146
|
+
end
|
86
147
|
|
87
|
-
|
88
|
-
|
89
|
-
image.bands == 2 ||
|
90
|
-
(image.bands == 4 && image.interpretation != :cmyk) ||
|
91
|
-
image.bands > 4
|
148
|
+
image = image.conv(sharpen, precision: :integer) if sharpen
|
149
|
+
image
|
92
150
|
end
|
93
151
|
|
152
|
+
# Hack to allow omitting one dimension.
|
94
153
|
def default_dimensions(width, height)
|
95
154
|
raise Error, "either width or height must be specified" unless width || height
|
96
155
|
|
97
156
|
[width || ::Vips::MAX_COORD, height || ::Vips::MAX_COORD]
|
98
157
|
end
|
99
158
|
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
159
|
+
# Converts the image on disk in various forms into a Vips::Image object.
|
160
|
+
def convert_to_image(object, name)
|
161
|
+
return object if object.is_a?(::Vips::Image)
|
162
|
+
|
163
|
+
if object.is_a?(String)
|
164
|
+
path = object
|
165
|
+
elsif object.respond_to?(:to_path)
|
166
|
+
path = object.to_path
|
167
|
+
elsif object.respond_to?(:path)
|
168
|
+
path = object.path
|
169
|
+
else
|
170
|
+
raise ArgumentError, "#{name} must be a Vips::Image, String, Pathname, or respond to #path"
|
171
|
+
end
|
104
172
|
|
105
|
-
|
106
|
-
saver = ::Vips.vips_foreign_find_save(destination_path)
|
107
|
-
saver ? select_valid_options(saver, options) : options
|
173
|
+
::Vips::Image.new_from_file(path)
|
108
174
|
end
|
109
175
|
|
110
|
-
|
111
|
-
|
176
|
+
module Utils
|
177
|
+
module_function
|
112
178
|
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
179
|
+
# libvips uses various loaders depending on the input format.
|
180
|
+
def select_valid_loader_options(source_path, options)
|
181
|
+
loader = ::Vips.vips_foreign_find_load(source_path)
|
182
|
+
loader ? select_valid_options(loader, options) : options
|
183
|
+
end
|
117
184
|
|
118
|
-
options
|
185
|
+
# Filters out unknown options for saving images.
|
186
|
+
def select_valid_saver_options(destination_path, options)
|
187
|
+
saver = ::Vips.vips_foreign_find_save(destination_path)
|
188
|
+
saver ? select_valid_options(saver, options) : options
|
189
|
+
end
|
190
|
+
|
191
|
+
# libvips uses various loaders and savers depending on the input and
|
192
|
+
# output image format. Each of these loaders and savers accept slightly
|
193
|
+
# different options, so to allow the user to be able to specify options
|
194
|
+
# for a specific loader/saver and have it ignored for other
|
195
|
+
# loaders/savers, we do some introspection and filter out options that
|
196
|
+
# don't exist for a particular loader or saver.
|
197
|
+
def select_valid_options(operation_name, options)
|
198
|
+
introspect = ::Vips::Introspect.get(operation_name)
|
199
|
+
operation_options = introspect.optional_input.keys.map(&:to_sym)
|
200
|
+
|
201
|
+
options.select { |name, value| operation_options.include?(name) }
|
202
|
+
end
|
119
203
|
end
|
120
204
|
end
|
121
205
|
end
|
metadata
CHANGED
@@ -1,36 +1,42 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: image_processing
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.2
|
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:
|
11
|
+
date: 2022-03-01 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: mini_magick
|
15
15
|
requirement: !ruby/object:Gem::Requirement
|
16
16
|
requirements:
|
17
|
-
- - "
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: 4.9.5
|
20
|
+
- - "<"
|
18
21
|
- !ruby/object:Gem::Version
|
19
|
-
version: '
|
22
|
+
version: '5'
|
20
23
|
type: :runtime
|
21
24
|
prerelease: false
|
22
25
|
version_requirements: !ruby/object:Gem::Requirement
|
23
26
|
requirements:
|
24
|
-
- - "
|
27
|
+
- - ">="
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: 4.9.5
|
30
|
+
- - "<"
|
25
31
|
- !ruby/object:Gem::Version
|
26
|
-
version: '
|
32
|
+
version: '5'
|
27
33
|
- !ruby/object:Gem::Dependency
|
28
34
|
name: ruby-vips
|
29
35
|
requirement: !ruby/object:Gem::Requirement
|
30
36
|
requirements:
|
31
37
|
- - ">="
|
32
38
|
- !ruby/object:Gem::Version
|
33
|
-
version: 2.0.
|
39
|
+
version: 2.0.17
|
34
40
|
- - "<"
|
35
41
|
- !ruby/object:Gem::Version
|
36
42
|
version: '3'
|
@@ -40,7 +46,7 @@ dependencies:
|
|
40
46
|
requirements:
|
41
47
|
- - ">="
|
42
48
|
- !ruby/object:Gem::Version
|
43
|
-
version: 2.0.
|
49
|
+
version: 2.0.17
|
44
50
|
- - "<"
|
45
51
|
- !ruby/object:Gem::Version
|
46
52
|
version: '3'
|
@@ -101,7 +107,7 @@ dependencies:
|
|
101
107
|
- !ruby/object:Gem::Version
|
102
108
|
version: '0'
|
103
109
|
- !ruby/object:Gem::Dependency
|
104
|
-
name:
|
110
|
+
name: dhash-vips
|
105
111
|
requirement: !ruby/object:Gem::Requirement
|
106
112
|
requirements:
|
107
113
|
- - ">="
|
@@ -114,7 +120,8 @@ dependencies:
|
|
114
120
|
- - ">="
|
115
121
|
- !ruby/object:Gem::Version
|
116
122
|
version: '0'
|
117
|
-
description:
|
123
|
+
description: High-level wrapper for processing images for the web with ImageMagick
|
124
|
+
or libvips.
|
118
125
|
email:
|
119
126
|
- janko.marohnic@gmail.com
|
120
127
|
executables: []
|
@@ -133,11 +140,11 @@ files:
|
|
133
140
|
- lib/image_processing/processor.rb
|
134
141
|
- lib/image_processing/version.rb
|
135
142
|
- lib/image_processing/vips.rb
|
136
|
-
homepage: https://github.com/janko
|
143
|
+
homepage: https://github.com/janko/image_processing
|
137
144
|
licenses:
|
138
145
|
- MIT
|
139
146
|
metadata: {}
|
140
|
-
post_install_message:
|
147
|
+
post_install_message:
|
141
148
|
rdoc_options: []
|
142
149
|
require_paths:
|
143
150
|
- lib
|
@@ -145,16 +152,16 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
145
152
|
requirements:
|
146
153
|
- - ">="
|
147
154
|
- !ruby/object:Gem::Version
|
148
|
-
version: '2.
|
155
|
+
version: '2.3'
|
149
156
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
150
157
|
requirements:
|
151
158
|
- - ">="
|
152
159
|
- !ruby/object:Gem::Version
|
153
160
|
version: '0'
|
154
161
|
requirements: []
|
155
|
-
|
156
|
-
|
157
|
-
signing_key:
|
162
|
+
rubygems_version: 3.3.3
|
163
|
+
signing_key:
|
158
164
|
specification_version: 4
|
159
|
-
summary:
|
165
|
+
summary: High-level wrapper for processing images for the web with ImageMagick or
|
166
|
+
libvips.
|
160
167
|
test_files: []
|