image_processing 0.11.1 → 0.11.2

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of image_processing might be problematic. Click here for more details.

checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 6bbfdce2d8d8f1b750b61f241ee2b73238c87f0f
4
- data.tar.gz: 357ac76e747cf48055673ef1d023268cade403c8
3
+ metadata.gz: 99835dc44c17bab77f81bca431807996d31042d2
4
+ data.tar.gz: c6b0c1b08d70e871a90f63ae66e5a501edc7c521
5
5
  SHA512:
6
- metadata.gz: 365f69bc69fa37a072f439c810f45ec421504a8edb9edbc73ae6306d9ed7391153c343b3aae4fb7950b8520b01fe03dcd0bc55f45a83eb5c459a33e40b9191b0
7
- data.tar.gz: 4800c9b7f470cb5246543c985854a56edee922508b3882f41002312801d0372b8b47a583e4e1aad628e0cbbe5e3df8be38c27f7fdac8547fdc1e52d13a09b9d6
6
+ metadata.gz: 529a954d4f587f756e55aa5316743f34747c5786c0c8ec729db1a9977c24bc5304575e9a96394d5270f234e180240fbd0f33035c0640e89993687377df4e744d
7
+ data.tar.gz: b6752c0ec43316e402b30bd54bd714b392bba09aac391979d4502c54f915cce2ceb989fcf3b366982c16084cc5208fa2168bb67a0d2097ba98b8c1a126fa966b
data/CHANGELOG.md CHANGED
@@ -1,4 +1,14 @@
1
- ## 0.11.1 (2017-03-27)
1
+ ## 0.11.2 (2018-03-31)
2
+
3
+ * [minimagick] Avoid `#resize_*` operations stripping data by switching back to `-resize` (@janko-m)
4
+
5
+ * [core] Make sure an empty destination file doesn't remain on processing errors when `:destination` is used (@janko-m)
6
+
7
+ * [vips] Fix `:alpha` not correctly adding alpha for certain types of images (@janko-m)
8
+
9
+ * [minimagick] Drop official support for GraphicsMagick (@janko-m)
10
+
11
+ ## 0.11.1 (2018-03-27)
2
12
 
3
13
  * [minimagick] Rename `#limit` to `#limits` to still allow adding `-limit` arguments directly (@janko-m)
4
14
 
data/README.md CHANGED
@@ -1,8 +1,7 @@
1
1
  # ImageProcessing
2
2
 
3
3
  Provides higher-level image processing functionality that is commonly needed
4
- when accepting user uploads. Supports processing with [VIPS] and
5
- [ImageMagick]/[GraphicsMagick].
4
+ when accepting user uploads. Supports processing with [libvips] and [ImageMagick].
6
5
 
7
6
  The goal of this project is to have a single place where common image
8
7
  processing helper methods are maintained, instead of Paperclip, CarrierWave,
@@ -11,7 +10,7 @@ Refile, Dragonfly and ActiveStorage each implementing their own versions.
11
10
  ## Installation
12
11
 
13
12
  ```rb
14
- gem "image_processing", "~> 0.10"
13
+ gem "image_processing", "~> 0.11"
15
14
  ```
16
15
 
17
16
  ## Usage
@@ -103,17 +102,17 @@ See the **[wiki]** for additional "How To" guides for common scenarios.
103
102
 
104
103
  ## Contributing
105
104
 
106
- Test suite requires `imagemagick`, `graphicsmagick` and `libvips` to be
107
- installed. On Mac OS you can install them with Homebrew:
105
+ Test suite requires `imagemagick` and `libvips` to be installed. On Mac OS you
106
+ can install them with Homebrew:
108
107
 
109
108
  ```
110
- $ brew install imagemagick graphicsmagick vips
109
+ $ brew install imagemagick vips
111
110
  ```
112
111
 
113
112
  Afterwards you can run tests with
114
113
 
115
114
  ```
116
- $ rake test
115
+ $ bundle exec rake test
117
116
  ```
118
117
 
119
118
  ## Credits
@@ -125,9 +124,8 @@ The `ImageProcessing::MiniMagick` functionality was extracted from
125
124
 
126
125
  [MIT](LICENSE.txt)
127
126
 
127
+ [libvps]: http://jcupitt.github.io/libvips/
128
128
  [ImageMagick]: https://www.imagemagick.org
129
- [GraphicsMagick]: http://www.graphicsmagick.org
130
- [VIPS]: http://jcupitt.github.io/libvips/
131
129
  [`ImageProcessing::Vips`]: /doc/vips.md#imageprocessingvips
132
130
  [`ImageProcessing::MiniMagick`]: /doc/minimagick.md#imageprocessingminimagick
133
131
  [refile-mini_magick]: https://github.com/refile/refile-mini_magick
@@ -16,6 +16,7 @@ Gem::Specification.new do |spec|
16
16
  spec.files = Dir["README.md", "LICENSE.txt", "CHANGELOG.md", "lib/**/*.rb", "*.gemspec"]
17
17
  spec.require_paths = ["lib"]
18
18
 
19
+ spec.add_development_dependency "rake"
19
20
  spec.add_development_dependency "minitest", "~> 5.8"
20
21
  spec.add_development_dependency "minitest-hooks", ">= 1.4.2"
21
22
  spec.add_development_dependency "minispec-metadata"
@@ -1,5 +1,7 @@
1
1
  require "image_processing/chainable"
2
+ require "image_processing/builder"
2
3
  require "image_processing/pipeline"
4
+ require "image_processing/processor"
3
5
  require "image_processing/version"
4
6
 
5
7
  module ImageProcessing
@@ -0,0 +1,13 @@
1
+ module ImageProcessing
2
+ class Builder
3
+ include Chainable
4
+
5
+ def initialize(options)
6
+ @default_options = options
7
+ end
8
+
9
+ def call!(**options)
10
+ Pipeline.new(default_options).call(**options)
11
+ end
12
+ end
13
+ end
@@ -29,7 +29,7 @@ module ImageProcessing
29
29
 
30
30
  def method_missing(name, *args)
31
31
  if name.to_s.end_with?("!")
32
- send(name.to_s.chomp("!"), *args).call!
32
+ send(name.to_s.chomp("!"), *args).call
33
33
  elsif name.to_s.end_with?("?")
34
34
  super
35
35
  else
@@ -37,26 +37,28 @@ module ImageProcessing
37
37
  end
38
38
  end
39
39
 
40
- def call(file = nil, **call_options)
40
+ def call(file = nil, destination: nil, **call_options)
41
41
  options = default_options
42
42
  options = options.merge(source: file) if file
43
+ options = options.merge(destination: destination) if destination
43
44
 
44
45
  branch(options).call!(**call_options)
45
46
  end
46
47
 
47
48
  def branch(options)
48
- options = options.merge(processor: self::Processor) if self.is_a?(Module)
49
- Pipeline.new(options)
49
+ options = options.merge(processor_class: self::Processor) unless self.is_a?(Builder)
50
+
51
+ Builder.new(options)
50
52
  end
51
53
 
52
54
  def default_options
53
55
  @default_options ||= {
54
- source: nil,
55
- loader: {},
56
- saver: {},
57
- format: nil,
58
- operations: [],
59
- processor: nil,
56
+ source: nil,
57
+ loader: {},
58
+ saver: {},
59
+ format: nil,
60
+ operations: [],
61
+ processor_class: nil,
60
62
  }
61
63
  end
62
64
  end
@@ -17,27 +17,19 @@ module ImageProcessing
17
17
  false
18
18
  end
19
19
 
20
- class Processor
20
+ class Processor < ImageProcessing::Processor
21
21
  IMAGE_CLASS = ::MiniMagick::Tool
22
22
 
23
- def apply_operation(name, magick, *args)
24
- if respond_to?(name)
25
- public_send(name, magick, *args)
26
- else
27
- magick.send(name, *args)
28
- end
29
- end
30
-
31
23
  def resize_to_limit(magick, width, height)
32
- magick.thumbnail "#{width}x#{height}>"
24
+ magick.resize "#{width}x#{height}>"
33
25
  end
34
26
 
35
27
  def resize_to_fit(magick, width, height)
36
- magick.thumbnail "#{width}x#{height}"
28
+ magick.resize "#{width}x#{height}"
37
29
  end
38
30
 
39
31
  def resize_to_fill(magick, width, height, gravity: "Center")
40
- magick.thumbnail "#{width}x#{height}^"
32
+ magick.resize "#{width}x#{height}^"
41
33
  magick.gravity gravity
42
34
  magick.background "rgba(255,255,255,0.0)" # transparent
43
35
  magick.extent "#{width}x#{height}"
@@ -46,7 +38,7 @@ module ImageProcessing
46
38
  def resize_and_pad(magick, width, height, background: :transparent, gravity: "Center")
47
39
  background = "rgba(255,255,255,0.0)" if background.to_s == "transparent"
48
40
 
49
- magick.thumbnail "#{width}x#{height}"
41
+ magick.resize "#{width}x#{height}"
50
42
  magick.background background
51
43
  magick.gravity gravity
52
44
  magick.extent "#{width}x#{height}"
@@ -2,58 +2,91 @@ require "tempfile"
2
2
 
3
3
  module ImageProcessing
4
4
  class Pipeline
5
- include Chainable
5
+ DEFAULT_FORMAT = "jpg"
6
+
7
+ attr_reader :source, :loader, :saver, :format, :operations, :processor_class, :destination
6
8
 
7
9
  def initialize(options)
8
- @default_options = options
10
+ options.each do |name, value|
11
+ value = normalize_source(value, options) if name == :source
12
+ instance_variable_set(:"@#{name}", value)
13
+ end
9
14
  end
10
15
 
11
- def call!(save: true, destination: nil)
12
- fail Error, "source file is not provided" unless default_options[:source]
16
+ def call(save: true)
17
+ processor = processor_class.new(self)
18
+ image = processor.load_image(source, **loader)
13
19
 
14
- image_class = default_options[:processor]::IMAGE_CLASS
20
+ operations.each do |name, args|
21
+ image = processor.apply_operation(name, image, *args)
22
+ end
15
23
 
16
- if default_options[:source].is_a?(image_class)
17
- source = default_options[:source]
18
- elsif default_options[:source].is_a?(String)
19
- source = default_options[:source]
20
- elsif default_options[:source].respond_to?(:path)
21
- source = default_options[:source].path
22
- elsif default_options[:source].respond_to?(:to_path)
23
- source = default_options[:source].to_path
24
+ if save == false
25
+ image
26
+ elsif destination
27
+ handle_destination do
28
+ processor.save_image(image, destination, **saver)
29
+ end
24
30
  else
25
- fail Error, "source file needs to respond to #path, or be a String, a Pathname, or a #{image_class} object"
31
+ create_tempfile do |tempfile|
32
+ processor.save_image(image, tempfile.path, **saver)
33
+ end
26
34
  end
35
+ end
27
36
 
28
- processor = default_options[:processor].new
29
- image = processor.load_image(source, default_options[:loader])
37
+ def source_path
38
+ source if source.is_a?(String)
39
+ end
30
40
 
31
- default_options[:operations].each do |name, args|
32
- if name == :custom
33
- image = args.first.call(image) || image
34
- else
35
- image = processor.apply_operation(name, image, *args)
36
- end
37
- end
41
+ def destination_format
42
+ format = File.extname(destination)[1..-1] if destination
43
+ format ||= self.format
44
+ format ||= File.extname(source_path)[1..-1] if source_path
38
45
 
39
- return image unless save
46
+ format || DEFAULT_FORMAT
47
+ end
40
48
 
41
- return processor.save_image(image, destination, default_options[:saver]) if destination
49
+ private
42
50
 
43
- source_path = source if source.is_a?(String)
44
- format = default_options[:format] || File.extname(source_path.to_s)[1..-1] || "jpg"
51
+ def create_tempfile
52
+ tempfile = Tempfile.new(["image_processing", ".#{destination_format}"], binmode: true)
45
53
 
46
- result = Tempfile.new(["image_processing", ".#{format}"], binmode: true)
54
+ yield tempfile
47
55
 
48
- begin
49
- processor.save_image(image, result.path, default_options[:saver])
50
- rescue
51
- result.close!
52
- raise
53
- end
56
+ tempfile.open
57
+ tempfile
58
+ rescue
59
+ tempfile.close! if tempfile
60
+ raise
61
+ end
62
+
63
+ # In case of processing errors, both libvips and imagemagick will leave the
64
+ # empty destination file they created, so this method makes sure it is
65
+ # deleted in case an exception is raised on saving the image.
66
+ def handle_destination
67
+ destination_existed = File.exist?(destination)
68
+ yield
69
+ rescue
70
+ File.delete(destination) if File.exist?(destination) && !destination_existed
71
+ raise
72
+ end
54
73
 
55
- result.open
56
- result
74
+ def normalize_source(source, options)
75
+ fail Error, "source file is not provided" unless source
76
+
77
+ image_class = options[:processor_class]::IMAGE_CLASS
78
+
79
+ if source.is_a?(image_class)
80
+ source
81
+ elsif source.is_a?(String)
82
+ source
83
+ elsif source.respond_to?(:path)
84
+ source.path
85
+ elsif source.respond_to?(:to_path)
86
+ source.to_path
87
+ else
88
+ fail Error, "source file needs to respond to #path, or be a String, a Pathname, or a #{image_class} object"
89
+ end
57
90
  end
58
91
  end
59
92
  end
@@ -0,0 +1,23 @@
1
+ module ImageProcessing
2
+ class Processor
3
+ def initialize(pipeline)
4
+ @pipeline = pipeline
5
+ end
6
+
7
+ def apply_operation(name, image, *args)
8
+ if respond_to?(name)
9
+ public_send(name, image, *args)
10
+ else
11
+ image.send(name, *args)
12
+ end
13
+ end
14
+
15
+ def custom(image, block)
16
+ block.call(image) || image
17
+ end
18
+
19
+ private
20
+
21
+ attr_reader :pipeline
22
+ end
23
+ end
@@ -1,3 +1,3 @@
1
1
  module ImageProcessing
2
- VERSION = "0.11.1"
2
+ VERSION = "0.11.2"
3
3
  end
@@ -14,19 +14,15 @@ module ImageProcessing
14
14
  false
15
15
  end
16
16
 
17
- class Processor
17
+ class Processor < ImageProcessing::Processor
18
18
  IMAGE_CLASS = ::Vips::Image
19
19
  # libvips has this arbitrary number as a sanity-check upper bound on image
20
20
  # size.
21
21
  MAX_COORD = 10_000_000
22
22
 
23
23
  def apply_operation(name, image, *args)
24
- if respond_to?(name)
25
- public_send(name, image, *args)
26
- else
27
- result = image.send(name, *args)
28
- result.is_a?(::Vips::Image) ? result : image
29
- end
24
+ result = super
25
+ result.is_a?(::Vips::Image) ? result : image
30
26
  end
31
27
 
32
28
  def resize_to_limit(image, width, height, **options)
@@ -48,7 +44,7 @@ module ImageProcessing
48
44
  embed_options.reject! { |name, value| value.nil? }
49
45
 
50
46
  image = image.thumbnail_image(width, height: height, **options)
51
- image = image.bandjoin(255) if alpha && image.bands == 3
47
+ image = add_alpha(image) if alpha && !has_alpha?(image)
52
48
  image.gravity(gravity, width, height, **embed_options)
53
49
  end
54
50
 
@@ -74,6 +70,19 @@ module ImageProcessing
74
70
 
75
71
  private
76
72
 
73
+ # Port of libvips' vips_addalpha().
74
+ def add_alpha(image)
75
+ max_alpha = (image.interpretation == :grey16 || image.interpretation == :rgb16) ? 65535 : 255
76
+ image.bandjoin(max_alpha)
77
+ end
78
+
79
+ # Port of libvips' vips_hasalpha().
80
+ def has_alpha?(image)
81
+ image.bands == 2 ||
82
+ (image.bands == 4 && image.interpretation != :cmyk) ||
83
+ image.bands > 4
84
+ end
85
+
77
86
  def default_dimensions(width, height)
78
87
  raise Error, "either width or height must be specified" unless width || height
79
88
 
metadata CHANGED
@@ -1,15 +1,29 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: image_processing
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.11.1
4
+ version: 0.11.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Janko Marohnić
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2018-03-27 00:00:00.000000000 Z
11
+ date: 2018-03-30 00:00:00.000000000 Z
12
12
  dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rake
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
13
27
  - !ruby/object:Gem::Dependency
14
28
  name: minitest
15
29
  requirement: !ruby/object:Gem::Requirement
@@ -106,10 +120,12 @@ files:
106
120
  - README.md
107
121
  - image_processing.gemspec
108
122
  - lib/image_processing.rb
123
+ - lib/image_processing/builder.rb
109
124
  - lib/image_processing/chainable.rb
110
125
  - lib/image_processing/mini_magick.rb
111
126
  - lib/image_processing/mini_magick/deprecated_api.rb
112
127
  - lib/image_processing/pipeline.rb
128
+ - lib/image_processing/processor.rb
113
129
  - lib/image_processing/version.rb
114
130
  - lib/image_processing/vips.rb
115
131
  homepage: https://github.com/janko-m/image_processing