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.
@@ -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: []