image_processing 1.2.0 → 1.12.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
- IMAGE_CLASS = ::Vips::Image
19
- # default sharpening mask that provides a fast and mild sharpen
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
- def apply_operation(name, image, *args)
25
- result = super
26
- result.is_a?(::Vips::Image) ? result : image
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
- def resize_to_limit(image, width, height, **options)
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(image, width, height, size: :down, **options)
70
+ thumbnail(width, height, size: :down, **options)
32
71
  end
33
72
 
34
- def resize_to_fit(image, width, height, **options)
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(image, width, height, **options)
76
+ thumbnail(width, height, **options)
37
77
  end
38
78
 
39
- def resize_to_fill(image, width, height, **options)
40
- thumbnail(image, width, height, crop: :centre, **options)
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
- def resize_and_pad(image, width, height, gravity: "centre", extend: nil, background: nil, alpha: nil, **options)
44
- embed_options = { extend: extend, background: background }
45
- embed_options.reject! { |name, value| value.nil? }
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
- image = thumbnail(image, width, height, **options)
48
- image = add_alpha(image) if alpha && !has_alpha?(image)
49
- image.gravity(gravity, width, height, **embed_options)
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
- def load_image(path_or_image, autorot: true, **options)
53
- if path_or_image.is_a?(::Vips::Image)
54
- image = path_or_image
55
- else
56
- source_path = path_or_image
57
- options = select_valid_loader_options(source_path, options)
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 = ::Vips::Image.new_from_file(source_path, **options)
106
+ return image.composite(overlay, _mode, **options)
60
107
  end
61
108
 
62
- image = image.autorot if autorot && !options.key?(:autorotate)
63
- image
64
- end
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
- def save_image(image, destination_path, quality: nil, **options)
67
- options = options.merge(Q: quality) if quality
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
- image.write_to_file(destination_path, **options)
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
- def thumbnail(image, width, height, sharpen: SHARPEN_MASK, **options)
76
- image = image.thumbnail_image(width, height: height, **options)
77
- image = image.conv(sharpen) if sharpen
78
- image
79
- end
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
- # Port of libvips' vips_addalpha().
82
- def add_alpha(image)
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
- # Port of libvips' vips_hasalpha().
88
- def has_alpha?(image)
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
- def select_valid_loader_options(source_path, options)
101
- loader = ::Vips.vips_foreign_find_load(source_path)
102
- loader ? select_valid_options(loader, options) : options
103
- end
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
- def select_valid_saver_options(destination_path, options)
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
- def select_valid_options(operation_name, options)
111
- operation = ::Vips::Operation.new(operation_name)
176
+ module Utils
177
+ module_function
112
178
 
113
- operation_options = operation.get_construct_args
114
- .select { |name, flags| (flags & ::Vips::ARGUMENT_INPUT) != 0 }
115
- .select { |name, flags| (flags & ::Vips::ARGUMENT_REQUIRED) == 0 }
116
- .map(&:first).map(&:to_sym)
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.select { |name, value| operation_options.include?(name) }
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.0
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: 2018-04-18 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
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: '4.0'
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: '4.0'
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.10
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.10
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: phashion
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: 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.
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-m/image_processing
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.0'
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
- rubyforge_project:
156
- rubygems_version: 2.7.6
157
- signing_key:
162
+ rubygems_version: 3.3.3
163
+ signing_key:
158
164
  specification_version: 4
159
- 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.
160
167
  test_files: []