jr-paperclip 7.3.1 → 8.0.0.beta.1

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.
Files changed (38) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/tests.yml +3 -1
  3. data/CONTRIBUTING.md +1 -1
  4. data/Gemfile +1 -0
  5. data/NEWS +13 -0
  6. data/README.md +116 -6
  7. data/UPGRADING +5 -0
  8. data/VIPS_MIGRATION_GUIDE.md +131 -0
  9. data/features/basic_integration.feature +27 -0
  10. data/features/step_definitions/attachment_steps.rb +17 -0
  11. data/gemfiles/7.0.gemfile +1 -0
  12. data/gemfiles/7.1.gemfile +1 -0
  13. data/gemfiles/7.2.gemfile +1 -0
  14. data/gemfiles/8.0.gemfile +1 -0
  15. data/gemfiles/8.1.gemfile +1 -0
  16. data/lib/paperclip/attachment.rb +3 -2
  17. data/lib/paperclip/errors.rb +4 -5
  18. data/lib/paperclip/geometry.rb +3 -3
  19. data/lib/paperclip/geometry_detector_factory.rb +52 -12
  20. data/lib/paperclip/helpers.rb +18 -0
  21. data/lib/paperclip/processor.rb +36 -4
  22. data/lib/paperclip/thumbnail.rb +568 -62
  23. data/lib/paperclip/version.rb +1 -1
  24. data/lib/paperclip.rb +26 -9
  25. data/paperclip.gemspec +2 -1
  26. data/spec/paperclip/attachment_definitions_spec.rb +300 -0
  27. data/spec/paperclip/attachment_spec.rb +1 -1
  28. data/spec/paperclip/geometry_detector_spec.rb +81 -32
  29. data/spec/paperclip/geometry_spec.rb +8 -5
  30. data/spec/paperclip/helpers_spec.rb +49 -0
  31. data/spec/paperclip/lazy_thumbnail_compatibility_spec.rb +266 -0
  32. data/spec/paperclip/processor_spec.rb +35 -1
  33. data/spec/paperclip/style_spec.rb +58 -0
  34. data/spec/paperclip/thumbnail_custom_options_spec.rb +173 -0
  35. data/spec/paperclip/thumbnail_loader_options_spec.rb +53 -0
  36. data/spec/paperclip/thumbnail_security_spec.rb +42 -0
  37. data/spec/paperclip/thumbnail_spec.rb +1127 -172
  38. metadata +34 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 86576949abbf8e615767bf012d920eeaccbfadcea328de29ee13f0122954c0c1
4
- data.tar.gz: aed22295a457aa08cf1b51abc27ac49d9025c9680102296461f8f94021f2231f
3
+ metadata.gz: '08e0a02cb6a9a3a1b126b744de6afe4654e9db7bf841bb710ac29b3cb702f5a2'
4
+ data.tar.gz: 5c3c603853869aa41d56026de0d71788f87f7b98ec4d8382adc811e23bc9eb42
5
5
  SHA512:
6
- metadata.gz: 53ed359d186530b8d3622a2fc20e07d7e6e41929760c17e104ddac82e3f5a2b88fd3c55ea9389d4c23a3412791c8691d01807652a065119b8e07fbba6e1fc972
7
- data.tar.gz: a5fe5b686f5b15bf5ade65038623bbfc275fc62a77dc366f871bd96ebd8db4a843b209bb8a9195a5b00169c057d7e5f817bba8cbdc0a918db17688d12f50eb68
6
+ metadata.gz: 9b71eb967b7cb2b0980e665c4886bbb5754bac409677b0af4e13578101dde22d710ab2bc11e31287dac3e99520f53f9e9b44fe79f5285f5fd5aa2cfe070edf43
7
+ data.tar.gz: ab955ec831d70c792bcaccbb41b5e2b6754fe0badddc1a52735d5a974f0982e3cdda316e5db9a274da73132482515ebf5b74775780d8e179e6d2493154370610
@@ -28,6 +28,8 @@ jobs:
28
28
  gemfile: gemfiles/8.0.gemfile
29
29
  - ruby: '3.4'
30
30
  gemfile: gemfiles/8.1.gemfile
31
+ - ruby: '4.0'
32
+ gemfile: gemfiles/8.1.gemfile
31
33
 
32
34
  env:
33
35
  BUNDLE_GEMFILE: ${{ matrix.gemfile }}
@@ -44,7 +46,7 @@ jobs:
44
46
  - name: Install dependencies
45
47
  run: |
46
48
  sudo apt-get update
47
- sudo apt-get install -y ghostscript imagemagick
49
+ sudo apt-get install -y ghostscript imagemagick libvips
48
50
  sudo rm -f /etc/ImageMagick-6/policy.xml
49
51
 
50
52
  - name: Run tests
data/CONTRIBUTING.md CHANGED
@@ -13,7 +13,7 @@ Here's a quick guide for contributing:
13
13
  1. Make sure you have ImageMagick and Ghostscript installed. See [this section](./README.md#image-processor) of the README.
14
14
 
15
15
  1. Run the tests. We only take pull requests with passing tests, and it's great
16
- to know that you have a clean slate: `bundle && bundle exec rake`
16
+ to know that you have a clean slate: `bundle && bundle exec appraisal rake`
17
17
 
18
18
  1. Add a test for your change. Only refactoring and documentation changes
19
19
  require no new tests. If you are adding functionality or fixing a bug, we need
data/Gemfile CHANGED
@@ -10,6 +10,7 @@ group :development, :test do
10
10
  gem "activerecord-import"
11
11
  gem "bootsnap", require: false
12
12
  gem "builder"
13
+ gem "image_processing", "~> 1.14"
13
14
  gem "listen"
14
15
  gem "rspec"
15
16
  gem "rubocop", require: false
data/NEWS CHANGED
@@ -1,3 +1,16 @@
1
+ 8.0.0.beta.1 (2026-01-09)
2
+
3
+ * Feature: Add libvips backend alongside ImageMagick via the image_processing gem
4
+ * Feature: Per-attachment and per-style backend selection (`:backend` option)
5
+ * Feature: ImageMagick 7 detection (uses `magick` command when available)
6
+ * Feature: Add `ALLOWED_IMAGEMAGICK_OPTIONS` security whitelist (GHSA-r4mg-4433-c7g3)
7
+ * Feature: Cross-platform convert_options support (-strip, -quality, -rotate, etc.)
8
+ * Documentation: Add VIPS_MIGRATION_GUIDE.md with step-by-step migration instructions
9
+ * Documentation: Expand README with backend configuration examples
10
+ * Chores: Add image_processing ~> 1.14 as runtime dependency
11
+ * Chores: CI now installs libvips for testing both backends
12
+ * Tests: Comprehensive test coverage for both backends, geometry modifiers, and convert options
13
+
1
14
  7.3.1 (2025-12-17)
2
15
  * Improvement: Relax Ruby requirement to 3.0 and optimize CI matrix
3
16
 
data/README.md CHANGED
@@ -6,7 +6,7 @@ Paperclip
6
6
  [![Tests](https://github.com/jukra/jr-paperclip/actions/workflows/tests.yml/badge.svg)](https://github.com/jukra/jr-paperclip/actions/workflows/tests.yml)
7
7
  [![reviewdog](https://github.com/jukra/jr-paperclip/actions/workflows/reviewdog.yml/badge.svg)](https://github.com/jukra/jr-paperclip/actions/workflows/reviewdog.yml)
8
8
 
9
- **Revived fork with active maintenance and support for Rails 7.0, 7.1, 8.0, and 8.1 on Ruby 3.2 and newer.**
9
+ **Revived fork with active maintenance and support for Rails 7.0, 7.1, 8.0, and 8.1 on Ruby 3.0 and newer.**
10
10
 
11
11
  If you are looking for older versions, please check the [original fork](https://github.com/kreeti/kt-paperclip).
12
12
 
@@ -58,6 +58,7 @@ Please feel free to contribute Issues and pull requests.
58
58
  - [Understanding Storage](#understanding-storage)
59
59
  - [IO Adapters](#io-adapters)
60
60
  - [Post Processing](#post-processing)
61
+ - [Image Processing Backends](#image-processing-backends)
61
62
  - [Custom Attachment Processors](#custom-attachment-processors)
62
63
  - [Events](#events)
63
64
  - [URI Obfuscation](#uri-obfuscation)
@@ -104,6 +105,10 @@ Paperclip now requires Ruby version **>= 2.3** and Rails version **>= 4.2**
104
105
 
105
106
  ### Image Processor
106
107
 
108
+ Paperclip supports two main image processing backends: **ImageMagick** (default) and **libvips** (recommended for performance).
109
+
110
+ #### ImageMagick
111
+
107
112
  [ImageMagick](http://www.imagemagick.org) must be installed and Paperclip must have access to it. To ensure
108
113
  that it does, on your command line, run `which convert` (one of the ImageMagick
109
114
  utilities). This will give you the path where that utility is installed. For
@@ -132,6 +137,32 @@ the following with apt-get:
132
137
 
133
138
  sudo apt-get install imagemagick -y
134
139
 
140
+ #### libvips (Recommended for Performance)
141
+
142
+ [libvips](https://www.libvips.org/) is significantly faster and uses less memory than ImageMagick.
143
+
144
+ To use libvips, install the system library:
145
+
146
+ ```bash
147
+ # macOS
148
+ brew install vips
149
+
150
+ # Ubuntu/Debian
151
+ sudo apt install libvips
152
+ ```
153
+
154
+ Then configure Paperclip to use it as the default backend in `config/initializers/paperclip.rb` (or in your environment configuration):
155
+
156
+ ```ruby
157
+ config.paperclip_defaults = {
158
+ backend: :vips
159
+ }
160
+ ```
161
+
162
+ You can also specify the backend per-attachment (see [Image Processing Backends](#image-processing-backends)).
163
+
164
+ **Note on Geometry Detection:** When `vips` is the active backend, Paperclip uses the ruby-vips gem to determine image dimensions instead of ImageMagick's `identify` command.
165
+
135
166
  ### `file`
136
167
 
137
168
  The Unix [`file` command](https://en.wikipedia.org/wiki/File_(command)) is required for content-type checking.
@@ -691,11 +722,11 @@ has_attached_file :avatar, styles: { thumb: ["32x32#", :png] }
691
722
 
692
723
  This will convert the "thumb" style to a 32x32 square in PNG format, regardless
693
724
  of what was uploaded. If the format is not specified, it is kept the same (e.g.
694
- JPGs will remain JPGs). `Paperclip::Thumbnail` uses ImageMagick to process
695
- images; [ImageMagick's geometry documentation](http://www.imagemagick.org/script/command-line-processing.php#geometry)
696
- has more information on the accepted style formats.
725
+ JPGs will remain JPGs). `Paperclip::Thumbnail` uses the [image_processing](https://github.com/janko/image_processing) gem to process images. This allows support for both ImageMagick (via MiniMagick) and libvips backends.
726
+ [ImageMagick's geometry documentation](http://www.imagemagick.org/script/command-line-processing.php#geometry)
727
+ has more information on the accepted style formats, which are generally supported by both backends in Paperclip.
697
728
 
698
- For more fine-grained control of the conversion process, `source_file_options` and `convert_options` can be used to pass flags and settings directly to ImageMagick's powerful Convert tool, [documented here](https://www.imagemagick.org/script/convert.php). For example:
729
+ For more fine-grained control of the conversion process, `source_file_options` and `convert_options` can be used. These options are parsed and applied to the `image_processing` pipeline.
699
730
 
700
731
  ```ruby
701
732
  has_attached_file :image, styles: { regular: ['800x800>', :png]},
@@ -703,6 +734,42 @@ has_attached_file :image, styles: { regular: ['800x800>', :png]},
703
734
  convert_options: { regular: "-posterize 3"}
704
735
  ```
705
736
 
737
+ Since Paperclip now delegates to the `image_processing` gem, you should refer to its documentation for available methods/operations for your chosen backend:
738
+
739
+ * **ImageMagick:** [ImageProcessing::MiniMagick documentation](https://github.com/janko/image_processing/blob/master/doc/minimagick.md)
740
+ * **libvips:** [ImageProcessing::Vips documentation](https://github.com/janko/image_processing/blob/master/doc/vips.md)
741
+
742
+ Common options like `-strip`, `-quality`, `-rotate`, `-blur` are supported on both backends, but backend-specific options (like `-density` for ImageMagick) might not be available or behave differently on libvips.
743
+
744
+ ### Cross-Platform Convert Options
745
+
746
+ Many common `convert_options` now work with **both** ImageMagick and libvips backends:
747
+
748
+ | Option | Description | Example |
749
+ |--------|-------------|---------|
750
+ | `-strip` | Remove metadata/EXIF | `convert_options: "-strip"` |
751
+ | `-quality N` | Output quality (1-100) | `convert_options: "-quality 80"` |
752
+ | `-rotate N` | Rotate by degrees | `convert_options: "-rotate 90"` |
753
+ | `-flip` | Vertical flip | `convert_options: "-flip"` |
754
+ | `-flop` | Horizontal flip | `convert_options: "-flop"` |
755
+ | `-blur 0xN` | Gaussian blur | `convert_options: "-blur 0x2"` |
756
+ | `-sharpen 0xN` | Sharpen image | `convert_options: "-sharpen 0x1"` |
757
+ | `-colorspace X` | Color space (Gray, sRGB, CMYK) | `convert_options: "-colorspace Gray"` |
758
+ | `-negate` | Invert colors | `convert_options: "-negate"` |
759
+ | `-flatten` | Flatten transparency | `convert_options: "-flatten"` |
760
+ | `-auto-orient` | Auto-rotate via EXIF | `convert_options: "-auto-orient"` |
761
+ | `-interlace X` | Progressive output | `convert_options: "-interlace Plane"` |
762
+
763
+ Example:
764
+
765
+ ```ruby
766
+ has_attached_file :avatar,
767
+ styles: { thumb: "100x100#" },
768
+ convert_options: { all: "-strip -quality 80" }
769
+ ```
770
+
771
+ **Note:** Some options only work with ImageMagick (e.g., `-density`, `-depth`, `-gravity`, `-crop`, `-trim`). When using the vips backend, these will be skipped with a warning logged.
772
+
706
773
  ImageMagick supports a number of environment variables for controlling its resource limits. For example, you can enforce memory or execution time limits by setting the following variables in your application's process environment:
707
774
 
708
775
  * `MAGICK_MEMORY_LIMIT=128MiB`
@@ -713,6 +780,48 @@ For a full list of variables and description, see [ImageMagick's resources docum
713
780
 
714
781
  ---
715
782
 
783
+ Image Processing Backends
784
+ -------------------------
785
+
786
+ jr-paperclip supports two image processing backends:
787
+
788
+ ### ImageMagick (Default)
789
+
790
+ The traditional backend, using ImageMagick via shell commands.
791
+
792
+ ```ruby
793
+ has_attached_file :avatar,
794
+ styles: { thumb: "100x100#" }
795
+ ```
796
+
797
+ ### libvips (Recommended for Performance)
798
+
799
+ libvips is significantly faster and uses less memory than ImageMagick.
800
+
801
+ **Usage:**
802
+
803
+ ```ruby
804
+ # Global default (in config/initializers/paperclip.rb or environment config)
805
+ config.paperclip_defaults = {
806
+ backend: :vips
807
+ }
808
+
809
+ # Or per-attachment
810
+ has_attached_file :avatar,
811
+ styles: { thumb: "100x100#" },
812
+ backend: :vips,
813
+ convert_options: { all: "-strip -quality 75" }
814
+
815
+ # Per-style backend selection (mix backends in one attachment)
816
+ has_attached_file :document,
817
+ styles: {
818
+ preview: { geometry: "800x800>", backend: :vips },
819
+ thumb: { geometry: "100x100#", backend: :image_magick }
820
+ }
821
+ ```
822
+
823
+ ---
824
+
716
825
  Custom Attachment Processors
717
826
  -------
718
827
 
@@ -1079,6 +1188,7 @@ Thank you to all [the contributors](https://github.com/jukra/jr-paperclip/graphs
1079
1188
  License
1080
1189
  -------
1081
1190
 
1082
- Copyright © 2020-2021 Kreeti Technologies Pvt. Ltd.
1191
+ Copyright (c) 2025- Jukka Rautanen
1192
+ Copyright © 2020-2025 Kreeti Technologies Pvt. Ltd.
1083
1193
  Copyright © 2008-2017 thoughtbot, inc.
1084
1194
  It is free software, and may be redistributed under the terms specified in the MIT-LICENSE file.
data/UPGRADING ADDED
@@ -0,0 +1,5 @@
1
+ jr-paperclip – HEADS UP (major changes)
2
+
3
+ This version adds libvips support as a high-performance alternative to ImageMagick.
4
+ It also introduces ImageMagick 7 compatibility and updates geometry detection behavior.
5
+ For migration instructions and details, see README.md and VIPS_MIGRATION_GUIDE.md.
@@ -0,0 +1,131 @@
1
+ # Migrating to the libvips Backend
2
+
3
+ Paperclip now supports [libvips](https://www.libvips.org/) via the `image_processing` gem. libvips is significantly faster and uses much less memory than ImageMagick, making it highly recommended for production environments.
4
+
5
+ This guide explains how to migrate your application to use libvips, either globally or gradually.
6
+
7
+ ## Prerequisites
8
+
9
+ You must install the libvips system library on your server or development machine:
10
+
11
+ ```bash
12
+ # macOS
13
+ brew install vips
14
+
15
+ # Ubuntu/Debian
16
+ sudo apt install libvips
17
+ ```
18
+
19
+ ## Step 1: Update your Gemfile
20
+
21
+ `jr-paperclip` already includes the `image_processing` gem, which automatically provides the `ruby-vips` and `mini_magick` bindings. You do **not** need to add these gems explicitly to your `Gemfile`.
22
+
23
+ Ensure you are using the latest version of the gem:
24
+
25
+ ```ruby
26
+ gem "jr-paperclip", "~> 8.0.0.beta"
27
+ ```
28
+
29
+ ## Step 2: Gradual Migration (Per-Attachment)
30
+
31
+ The safest way to migrate is one attachment at a time. This allows you to verify that the output remains consistent and doesn't interfere with existing custom processors.
32
+
33
+ Simply add the `backend: :vips` option to a specific attachment:
34
+
35
+ ```ruby
36
+ class User < ActiveRecord::Base
37
+ has_attached_file :avatar,
38
+ styles: { medium: "300x300>", thumb: "100x100#" },
39
+ backend: :vips
40
+ end
41
+ ```
42
+
43
+ Other attachments in your application will continue to use ImageMagick by default.
44
+
45
+ ## Step 3: Cross-Platform Convert Options
46
+
47
+ Good news! Many common `convert_options` now work with **both** ImageMagick and libvips backends. You can keep using them as-is:
48
+
49
+ ### Options That Work on Both Backends
50
+
51
+ | Option | Description |
52
+ |--------|-------------|
53
+ | `-strip` | Remove metadata/EXIF data |
54
+ | `-quality N` | Output quality (1-100) |
55
+ | `-rotate N` | Rotate by degrees |
56
+ | `-flip` | Vertical flip |
57
+ | `-flop` | Horizontal flip |
58
+ | `-blur 0xN` | Gaussian blur |
59
+ | `-sharpen 0xN` | Sharpen image |
60
+ | `-colorspace X` | Color space (Gray, sRGB, CMYK) |
61
+ | `-negate` | Invert colors |
62
+ | `-flatten` | Flatten transparency |
63
+ | `-auto-orient` | Auto-rotate via EXIF |
64
+ | `-interlace X` | Progressive/interlaced output |
65
+
66
+ **Example:**
67
+ ```ruby
68
+ has_attached_file :avatar,
69
+ styles: { thumb: "100x100#" },
70
+ backend: :vips,
71
+ convert_options: { all: "-strip -quality 80" }
72
+ ```
73
+
74
+ ### ImageMagick-Only Options
75
+
76
+ The following options only work with ImageMagick. When used with the vips backend, they will be skipped and a warning will be logged:
77
+
78
+ `-density`, `-depth`, `-gravity`, `-crop`, `-extent`, `-alpha`, `-background`, `-type`, `-posterize`, `-dither`, `-colors`, `-channel`, `-transpose`, `-transverse`, `-normalize`, `-equalize`, `-trim`, `-monochrome`
79
+
80
+ If you rely heavily on these options, consider keeping those specific attachments on the ImageMagick backend.
81
+
82
+ ## Step 4: Handling Custom Processors
83
+
84
+ If you have custom processors defined in `lib/paperclip`, they will continue to work even if you switch the primary backend to `:vips`, as long as they are still calling the `convert` or `identify` helper methods.
85
+
86
+ However, if you want to migrate a custom processor to libvips, you can now use the `vips`, `vips_image` and `vipsheader` helpers:
87
+
88
+ ```ruby
89
+ module Paperclip
90
+ class MyVipsProcessor < Processor
91
+ def make
92
+ # Use the new vips helper instead of convert
93
+ vips("thumbnail :src :dst 100",
94
+ src: File.expand_path(file.path),
95
+ dst: File.expand_path(dst.path)
96
+ )
97
+ dst
98
+ end
99
+ end
100
+ end
101
+ ```
102
+
103
+ ## Step 5: Global Migration
104
+
105
+ Once you have verified that your attachments and processors work correctly with libvips, you can switch the entire application over by updating your global configuration:
106
+
107
+ ```ruby
108
+ # config/initializers/paperclip.rb or config/environments/production.rb
109
+ config.paperclip_defaults = {
110
+ backend: :vips,
111
+ convert_options: { all: "-strip -quality 85" }
112
+ }
113
+ ```
114
+
115
+ You can also use per-style backend selection to mix backends within a single attachment:
116
+
117
+ ```ruby
118
+ has_attached_file :document,
119
+ styles: {
120
+ # Use vips for large previews (faster)
121
+ preview: { geometry: "800x800>", backend: :vips },
122
+ # Use ImageMagick for thumbnails needing specific options
123
+ thumb: { geometry: "100x100#", backend: :image_magick }
124
+ }
125
+ ```
126
+
127
+ ## Important Considerations
128
+
129
+ 1. **Output Parity**: While libvips aims for high quality, its resizing algorithms (Lanczos) may produce slightly different visual results than ImageMagick.
130
+ 2. **PDF/SVG Support**: libvips requires additional libraries (like `poppler` or `librsvg`) to process these formats. If you process complex vector formats, ensure the appropriate libraries are installed on your system.
131
+ 3. **Exotic Formats**: If you rely on very specific ImageMagick features (like specialized filters or complex layer manipulation), test those attachments thoroughly before switching.
@@ -83,3 +83,30 @@ Feature: Rails integration
83
83
  Then I should see "Name: something"
84
84
  And I should see an image with a path of "//s3.amazonaws.com/paperclip/attachments/original/5k.png"
85
85
  And the file at "//s3.amazonaws.com/paperclip/attachments/original/5k.png" should be uploaded to S3
86
+
87
+ Scenario: libvips integration test
88
+ Given I add this snippet to config/application.rb:
89
+ """
90
+ config.paperclip_defaults = {
91
+ :backend => :vips
92
+ }
93
+ """
94
+ And I attach :attachment with:
95
+ """
96
+ url: "/system/:attachment/:style/:filename",
97
+ styles: { thumb: "100x100#" }
98
+ """
99
+ And I start the rails application
100
+ And I overwrite "app/views/users/show.html.erb" with:
101
+ """
102
+ <p>Name: <%= @user.name %></p>
103
+ <p>Original: <%= image_tag @user.attachment.url(:original) %></p>
104
+ <p>Thumb: <%= image_tag @user.attachment.url(:thumb) %></p>
105
+ """
106
+ When I go to the new user page
107
+ And I fill in "Name" with "vips user"
108
+ And I attach the file "spec/support/fixtures/5k.png" to "Attachment"
109
+ And I press "Submit"
110
+ Then I should see "Name: vips user"
111
+ And the file at "/system/attachments/original/5k.png" should have dimensions "434x66"
112
+ And the file at "/system/attachments/thumb/5k.png" should have dimensions "100x100"
@@ -86,6 +86,23 @@ Then /^the attachment file "([^"]*)" should (not )?exist$/ do |filename, not_exi
86
86
  end
87
87
  end
88
88
 
89
+ Then /^the file at "([^"]*)" should have dimensions "([^"]*)"$/ do |web_path, dimension|
90
+ cd(".") do
91
+ local_path = "public#{web_path}"
92
+ # Try identify first, then vipsheader
93
+ require "shellwords"
94
+ escaped_path = Shellwords.escape(local_path)
95
+ geometry = `identify -format "%wx%h" #{escaped_path} 2>/dev/null`.strip
96
+ if geometry.empty?
97
+ width = `vipsheader -f width #{escaped_path} 2>/dev/null`.strip
98
+ height = `vipsheader -f height #{escaped_path} 2>/dev/null`.strip
99
+ geometry = "#{width}x#{height}"
100
+ end
101
+ raise "Could not determine dimensions for #{local_path}" if geometry == "x" || geometry.empty?
102
+ expect(geometry).to eq(dimension)
103
+ end
104
+ end
105
+
89
106
  Then /^I should have attachment columns for "([^"]*)"$/ do |attachment_name|
90
107
  cd(".") do
91
108
  columns = eval(`bundle exec rails runner "puts User.columns.map{ |column| [column.name, column.sql_type] }.inspect"`.strip)
data/gemfiles/7.0.gemfile CHANGED
@@ -11,6 +11,7 @@ group :development, :test do
11
11
  gem "activerecord-import"
12
12
  gem "bootsnap", require: false
13
13
  gem "builder"
14
+ gem "image_processing", "~> 1.14"
14
15
  gem "listen"
15
16
  gem "rspec"
16
17
  gem "rubocop", require: false
data/gemfiles/7.1.gemfile CHANGED
@@ -11,6 +11,7 @@ group :development, :test do
11
11
  gem "activerecord-import"
12
12
  gem "bootsnap", require: false
13
13
  gem "builder"
14
+ gem "image_processing", "~> 1.14"
14
15
  gem "listen"
15
16
  gem "rspec"
16
17
  gem "rubocop", require: false
data/gemfiles/7.2.gemfile CHANGED
@@ -11,6 +11,7 @@ group :development, :test do
11
11
  gem "activerecord-import"
12
12
  gem "bootsnap", require: false
13
13
  gem "builder"
14
+ gem "image_processing", "~> 1.14"
14
15
  gem "listen"
15
16
  gem "rspec"
16
17
  gem "rubocop", require: false
data/gemfiles/8.0.gemfile CHANGED
@@ -11,6 +11,7 @@ group :development, :test do
11
11
  gem "activerecord-import"
12
12
  gem "bootsnap", require: false
13
13
  gem "builder"
14
+ gem "image_processing", "~> 1.14"
14
15
  gem "listen"
15
16
  gem "rspec"
16
17
  gem "rubocop", require: false
data/gemfiles/8.1.gemfile CHANGED
@@ -11,6 +11,7 @@ group :development, :test do
11
11
  gem "activerecord-import"
12
12
  gem "bootsnap", require: false
13
13
  gem "builder"
14
+ gem "image_processing", "~> 1.14"
14
15
  gem "listen"
15
16
  gem "rspec"
16
17
  gem "rubocop", require: false
@@ -34,7 +34,8 @@ module Paperclip
34
34
  validate_media_type: true,
35
35
  adapter_options: { hash_digest: Digest::MD5 },
36
36
  check_validity_before_processing: true,
37
- return_file_attributes_on_destroy: false
37
+ return_file_attributes_on_destroy: false,
38
+ backend: Paperclip.options[:backend]
38
39
  }
39
40
  end
40
41
 
@@ -537,7 +538,7 @@ module Paperclip
537
538
  for(@queued_for_write[name], @options[:adapter_options])
538
539
  unadapted_file.close if unadapted_file.respond_to?(:close)
539
540
  @queued_for_write[name]
540
- rescue Paperclip::Errors::NotIdentifiedByImageMagickError => e
541
+ rescue Paperclip::Errors::NotIdentifiedByBackendError => e
541
542
  log("An error was received while processing: #{e.inspect}")
542
543
  (@errors[:processing] ||= []) << e.message if @options[:whiny]
543
544
  ensure
@@ -18,12 +18,11 @@ module Paperclip
18
18
  class MissingRequiredValidatorError < Paperclip::Error
19
19
  end
20
20
 
21
- # Will be thrown when ImageMagic cannot determine the uploaded file's
22
- # metadata, usually this would mean the file is not an image. If you are
23
- # consistently receiving this error on PDFs make sure that you have
24
- # installed Ghostscript.
25
- class NotIdentifiedByImageMagickError < Paperclip::Error
21
+ # Will be thrown when the backend cannot determine the uploaded file's
22
+ # metadata, usually this would mean the file is not an image.
23
+ class NotIdentifiedByBackendError < Paperclip::Error
26
24
  end
25
+ NotIdentifiedByImageMagickError = NotIdentifiedByBackendError
27
26
 
28
27
  # Will be thrown if the interpolation is creating an infinite loop. If you
29
28
  # are creating an interpolator which might cause an infinite loop, you
@@ -21,8 +21,8 @@ module Paperclip
21
21
  end
22
22
 
23
23
  # Extracts the Geometry from a file (or path to a file)
24
- def self.from_file(file)
25
- GeometryDetector.new(file).make
24
+ def self.from_file(file, backend = nil)
25
+ GeometryDetector.new(file, backend: backend).make
26
26
  end
27
27
 
28
28
  # Extracts the Geometry from a "WxH,O" string
@@ -84,7 +84,7 @@ module Paperclip
84
84
  to_s
85
85
  end
86
86
 
87
- # Returns the scaling and cropping geometries (in string-based ImageMagick format)
87
+ # Returns the scaling and cropping geometries (in string format)
88
88
  # neccessary to transform this Geometry into the Geometry given. If crop is true,
89
89
  # then it is assumed the destination Geometry will be the exact final resolution.
90
90
  # In this case, the source Geometry is scaled so that an image containing the
@@ -1,31 +1,75 @@
1
1
  module Paperclip
2
2
  class GeometryDetector
3
- def initialize(file)
3
+ def initialize(file, backend: nil)
4
4
  @file = file
5
+ @backend = backend
5
6
  raise_if_blank_file
6
7
  end
7
8
 
8
9
  def make
9
10
  geometry = GeometryParser.new(geometry_string.strip).make
10
- geometry || raise(Errors::NotIdentifiedByImageMagickError.new("Could not identify image size"))
11
+ geometry || raise(Errors::NotIdentifiedByBackendError.new("Could not identify image size"))
11
12
  end
12
13
 
13
14
  private
14
15
 
15
16
  def geometry_string
16
- orientation = Paperclip.options[:use_exif_orientation] ?
17
- "%[exif:orientation]" : "1"
17
+ if resolve_backend == :vips
18
+ vips_geometry_string
19
+ else
20
+ imagemagick_geometry_string
21
+ end
22
+ end
23
+
24
+ def resolve_backend
25
+ Paperclip.resolve_backend(@backend || Paperclip.options[:backend])
26
+ end
27
+
28
+ def imagemagick_geometry_string
29
+ orientation = if Paperclip.options[:use_exif_orientation]
30
+ "%[exif:orientation]"
31
+ else
32
+ "1"
33
+ end
18
34
  Paperclip.run(
19
- Paperclip.options[:is_windows] ? "magick identify" : "identify",
35
+ (Paperclip.options[:is_windows] || Paperclip.imagemagick7?) ? "magick identify" : "identify",
20
36
  "-format '%wx%h,#{orientation}' :file", {
21
- file: "#{path}[0]"
37
+ file: "#{path}[0]",
22
38
  },
23
39
  swallow_stderr: true
24
40
  )
25
41
  rescue Terrapin::ExitStatusError
26
42
  ""
27
43
  rescue Terrapin::CommandNotFoundError => e
28
- raise_because_imagemagick_missing
44
+ raise Errors::CommandNotFoundError.new("Could not run the `identify` command. Please install ImageMagick.")
45
+ end
46
+
47
+ def vips_geometry_string
48
+ begin
49
+ require "vips"
50
+ rescue LoadError => e
51
+ raise Errors::CommandNotFoundError.new("Could not load ruby-vips. Please install libvips and the image_processing gem.")
52
+ end
53
+
54
+ begin
55
+ # Use ruby-vips gem directly instead of shelling out to vipsheader
56
+ image = Vips::Image.new_from_file(path, access: :sequential)
57
+ width = image.width
58
+ height = image.height
59
+
60
+ orientation = "1"
61
+ if Paperclip.options[:use_exif_orientation]
62
+ begin
63
+ orientation = image.get("orientation").to_s
64
+ rescue Vips::Error
65
+ # Field might not exist
66
+ end
67
+ end
68
+
69
+ "#{width}x#{height},#{orientation}"
70
+ rescue Vips::Error
71
+ ""
72
+ end
29
73
  end
30
74
 
31
75
  def path
@@ -34,12 +78,8 @@ module Paperclip
34
78
 
35
79
  def raise_if_blank_file
36
80
  if path.blank?
37
- raise Errors::NotIdentifiedByImageMagickError.new("Cannot find the geometry of a file with a blank name")
81
+ raise Errors::NotIdentifiedByBackendError.new("Cannot find the geometry of a file with a blank name")
38
82
  end
39
83
  end
40
-
41
- def raise_because_imagemagick_missing
42
- raise Errors::CommandNotFoundError.new("Could not run the `identify` command. Please install ImageMagick.")
43
- end
44
84
  end
45
85
  end
@@ -56,5 +56,23 @@ module Paperclip
56
56
  def reset_duplicate_clash_check!
57
57
  @names_url = nil
58
58
  end
59
+
60
+ def imagemagick7?
61
+ return @imagemagick7 if instance_variable_defined?(:@imagemagick7)
62
+
63
+ @imagemagick7 = !!which("magick")
64
+ end
65
+
66
+ # https://github.com/minimagick/minimagick/blob/master/lib/mini_magick/utilities.rb#L9-L24
67
+ def which(cmd)
68
+ exts = ENV["PATHEXT"] ? ENV["PATHEXT"].split(";") : [""]
69
+ (ENV["PATH"] || "").split(File::PATH_SEPARATOR).each do |path|
70
+ exts.each do |ext|
71
+ exe = File.join(path, "#{cmd}#{ext}")
72
+ return exe if File.executable? exe
73
+ end
74
+ end
75
+ nil
76
+ end
59
77
  end
60
78
  end