image_processing 0.11.1 → 0.11.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.

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