aboutsummaryrefslogtreecommitdiffstats
path: root/activestorage
diff options
context:
space:
mode:
Diffstat (limited to 'activestorage')
-rw-r--r--activestorage/README.md2
-rw-r--r--activestorage/app/models/active_storage/variant.rb66
-rw-r--r--activestorage/app/models/active_storage/variation.rb72
-rw-r--r--activestorage/lib/active_storage.rb1
-rw-r--r--activestorage/lib/active_storage/engine.rb1
-rw-r--r--activestorage/test/models/variant_test.rb47
-rw-r--r--activestorage/test/test_helper.rb2
7 files changed, 125 insertions, 66 deletions
diff --git a/activestorage/README.md b/activestorage/README.md
index 85ab70dac6..242d5b8216 100644
--- a/activestorage/README.md
+++ b/activestorage/README.md
@@ -4,7 +4,7 @@ Active Storage makes it simple to upload and reference files in cloud services l
Files can be uploaded from the server to the cloud or directly from the client to the cloud.
-Image files can furthermore be transformed using on-demand variants for quality, aspect ratio, size, or any other [MiniMagick](https://github.com/minimagick/minimagick) supported transformation.
+Image files can furthermore be transformed using on-demand variants for quality, aspect ratio, size, or any other [MiniMagick](https://github.com/minimagick/minimagick) or [Vips](http://www.rubydoc.info/gems/ruby-vips/Vips/Image) supported transformation.
## Compared to other storage solutions
diff --git a/activestorage/app/models/active_storage/variant.rb b/activestorage/app/models/active_storage/variant.rb
index d84208419c..1cae2078f0 100644
--- a/activestorage/app/models/active_storage/variant.rb
+++ b/activestorage/app/models/active_storage/variant.rb
@@ -6,8 +6,18 @@ require "active_storage/downloading"
# These variants are used to create thumbnails, fixed-size avatars, or any other derivative image from the
# original.
#
-# Variants rely on {MiniMagick}[https://github.com/minimagick/minimagick] for the actual transformations
-# of the file, so you must add <tt>gem "mini_magick"</tt> to your Gemfile if you wish to use variants.
+# Variants rely on {ImageProcessing}[https://github.com/janko-m/image_processing] gem for the actual transformations
+# of the file, so you must add <tt>gem "image_processing"</tt> to your Gemfile if you wish to use variants. By
+# default, images will be processed with {ImageMagick}[http://imagemagick.org] using the
+# {MiniMagick}[https://github.com/minimagick/minimagick] gem, but you can also switch to the
+# {libvips}[http://jcupitt.github.io/libvips/] processor operated by the {ruby-vips}[https://github.com/jcupitt/ruby-vips]
+# gem).
+#
+# Rails.application.config.active_storage.processor
+# # => :mini_magick
+#
+# Rails.application.config.active_storage.processor = :vips
+# # => :vips
#
# Note that to create a variant it's necessary to download the entire blob file from the service and load it
# into memory. The larger the image, the more memory is used. Because of this process, you also want to be
@@ -18,7 +28,7 @@ require "active_storage/downloading"
# To refer to such a delayed on-demand variant, simply link to the variant through the resolved route provided
# by Active Storage like so:
#
-# <%= image_tag Current.user.avatar.variant(resize: "100x100") %>
+# <%= image_tag Current.user.avatar.variant(resize_to_fit: [100, 100]) %>
#
# This will create a URL for that specific blob with that specific variant, which the ActiveStorage::RepresentationsController
# can then produce on-demand.
@@ -27,15 +37,22 @@ require "active_storage/downloading"
# has already been processed and uploaded to the service, and, if so, just return that. Otherwise it will perform
# the transformations, upload the variant to the service, and return itself again. Example:
#
-# avatar.variant(resize: "100x100").processed.service_url
+# avatar.variant(resize_to_fit: [100, 100]).processed.service_url
#
# This will create and process a variant of the avatar blob that's constrained to a height and width of 100.
# Then it'll upload said variant to the service according to a derivative key of the blob and the transformations.
#
-# A list of all possible transformations is available at https://www.imagemagick.org/script/mogrify.php. You can
-# combine as many as you like freely:
+# Variant options are forwarded directly to the ImageProcessing gem. Visit the following links for a list of
+# available ImageProcessing commands and processor operations:
+#
+# * {ImageProcessing::MiniMagick}[https://github.com/janko-m/image_processing/blob/master/doc/minimagick.md#methods]
+# * {ImageMagick reference}[https://www.imagemagick.org/script/mogrify.php]
+# * {ImageProcessing::Vips}[https://github.com/janko-m/image_processing/blob/master/doc/vips.md#methods]
+# * {ruby-vips reference}[http://www.rubydoc.info/gems/ruby-vips/Vips/Image]
+#
+# You can combine as many of these options as you like freely:
#
-# avatar.variant(resize: "100x100", monochrome: true, flip: "-90")
+# avatar.variant(resize_to_fit: [100, 100], monochrome: true, flip: "-90")
class ActiveStorage::Variant
include ActiveStorage::Downloading
@@ -82,10 +99,10 @@ class ActiveStorage::Variant
end
def process
- open_image do |image|
- transform image
- format image
- upload image
+ download_blob_to_tempfile do |image|
+ variant = transform image
+ upload variant
+ variant.close!
end
end
@@ -102,31 +119,12 @@ class ActiveStorage::Variant
blob.content_type.presence_in(WEB_IMAGE_CONTENT_TYPES) || "image/png"
end
-
- def open_image(&block)
- image = download_image
-
- begin
- yield image
- ensure
- image.destroy!
- end
- end
-
- def download_image
- require "mini_magick"
- MiniMagick::Image.create(blob.filename.extension_with_delimiter) { |file| download_blob_to(file) }
- end
-
def transform(image)
- variation.transform(image)
- end
-
- def format(image)
- image.format("PNG") unless WEB_IMAGE_CONTENT_TYPES.include?(blob.content_type)
+ format = "png" unless WEB_IMAGE_CONTENT_TYPES.include?(blob.content_type)
+ variation.transform(image, format: format)
end
- def upload(image)
- File.open(image.path, "r") { |file| service.upload(key, file) }
+ def upload(file)
+ service.upload(key, file)
end
end
diff --git a/activestorage/app/models/active_storage/variation.rb b/activestorage/app/models/active_storage/variation.rb
index 12e7f9f0b5..3bdbc5bacb 100644
--- a/activestorage/app/models/active_storage/variation.rb
+++ b/activestorage/app/models/active_storage/variation.rb
@@ -8,15 +8,7 @@
#
# ActiveStorage::Variation.new(resize: "100x100", monochrome: true, trim: true, rotate: "-90")
#
-# You can also combine multiple transformations in one step, e.g. for center-weighted cropping:
-#
-# ActiveStorage::Variation.new(combine_options: {
-# resize: "100x100^",
-# gravity: "center",
-# crop: "100x100+0+0",
-# })
-#
-# A list of all possible transformations is available at https://www.imagemagick.org/script/mogrify.php.
+# The options map directly to {ImageProcessing}[https://github.com/janko-m/image_processing] commands.
class ActiveStorage::Variation
attr_reader :transformations
@@ -51,10 +43,49 @@ class ActiveStorage::Variation
@transformations = transformations
end
- # Accepts an open MiniMagick image instance, like what's returned by <tt>MiniMagick::Image.read(io)</tt>,
- # and performs the +transformations+ against it. The transformed image instance is then returned.
- def transform(image)
+ # Accepts a File object, performs the +transformations+ against it, and
+ # saves the transformed image into a temporary file. If +format+ is specified
+ # it will be the format of the result image, otherwise the result image
+ # retains the source format.
+ def transform(file, format: nil)
ActiveSupport::Notifications.instrument("transform.active_storage") do
+ if processor
+ image_processing_transform(file, format)
+ else
+ mini_magick_transform(file, format)
+ end
+ end
+ end
+
+ # Returns a signed key for all the +transformations+ that this variation was instantiated with.
+ def key
+ self.class.encode(transformations)
+ end
+
+ private
+ # Applies image transformations using the ImageProcessing gem.
+ def image_processing_transform(file, format)
+ operations = transformations.inject([]) do |list, (name, argument)|
+ if name.to_s == "combine_options"
+ ActiveSupport::Deprecation.warn("The ImageProcessing ActiveStorage variant backend doesn't need :combine_options, as it already generates a single MiniMagick command. In Rails 6.1 :combine_options will not be supported anymore.")
+ list.concat argument.to_a
+ else
+ list << [name, argument]
+ end
+ end
+
+ processor
+ .source(file)
+ .loader(page: 0)
+ .convert(format)
+ .apply(operations)
+ .call
+ end
+
+ # Applies image transformations using the MiniMagick gem.
+ def mini_magick_transform(file, format)
+ image = MiniMagick::Image.new(file.path, file)
+
transformations.each do |name, argument_or_subtransformations|
image.mogrify do |command|
if name.to_s == "combine_options"
@@ -66,15 +97,20 @@ class ActiveStorage::Variation
end
end
end
+
+ image.format(format) if format
+
+ image.tempfile.tap(&:open)
end
- end
- # Returns a signed key for all the +transformations+ that this variation was instantiated with.
- def key
- self.class.encode(transformations)
- end
+ # Returns the ImageProcessing processor class specified by `ActiveStorage.processor`.
+ def processor
+ require "image_processing"
+ ImageProcessing.const_get(ActiveStorage.processor.to_s.camelize) if ActiveStorage.processor
+ rescue LoadError
+ ActiveSupport::Deprecation.warn("Using mini_magick gem directly is deprecated and will be removed in Rails 6.1. Please add `gem 'image_processing', '~> 1.2'` to your Gemfile.")
+ end
- private
def pass_transform_argument(command, method, argument)
if eligible_argument?(argument)
command.public_send(method, argument)
diff --git a/activestorage/lib/active_storage.rb b/activestorage/lib/active_storage.rb
index e1bd974853..817948e675 100644
--- a/activestorage/lib/active_storage.rb
+++ b/activestorage/lib/active_storage.rb
@@ -45,6 +45,7 @@ module ActiveStorage
mattr_accessor :queue
mattr_accessor :previewers, default: []
mattr_accessor :analyzers, default: []
+ mattr_accessor :processor, default: :mini_magick
mattr_accessor :paths, default: {}
mattr_accessor :variable_content_types, default: []
mattr_accessor :content_types_to_serve_as_binary, default: []
diff --git a/activestorage/lib/active_storage/engine.rb b/activestorage/lib/active_storage/engine.rb
index 1385e2aa84..02719e4173 100644
--- a/activestorage/lib/active_storage/engine.rb
+++ b/activestorage/lib/active_storage/engine.rb
@@ -45,6 +45,7 @@ module ActiveStorage
config.after_initialize do |app|
ActiveStorage.logger = app.config.active_storage.logger || Rails.logger
ActiveStorage.queue = app.config.active_storage.queue
+ ActiveStorage.processor = app.config.active_storage.processor || :mini_magick
ActiveStorage.previewers = app.config.active_storage.previewers || []
ActiveStorage.analyzers = app.config.active_storage.analyzers || []
ActiveStorage.paths = app.config.active_storage.paths || {}
diff --git a/activestorage/test/models/variant_test.rb b/activestorage/test/models/variant_test.rb
index 0f3ada25c0..1af315b664 100644
--- a/activestorage/test/models/variant_test.rb
+++ b/activestorage/test/models/variant_test.rb
@@ -6,7 +6,7 @@ require "database/setup"
class ActiveStorage::VariantTest < ActiveSupport::TestCase
test "resized variation of JPEG blob" do
blob = create_file_blob(filename: "racecar.jpg")
- variant = blob.variant(resize: "100x100").processed
+ variant = blob.variant(resize_to_fit: [100, 100]).processed
assert_match(/racecar\.jpg/, variant.service_url)
image = read_image(variant)
@@ -16,7 +16,7 @@ class ActiveStorage::VariantTest < ActiveSupport::TestCase
test "resized and monochrome variation of JPEG blob" do
blob = create_file_blob(filename: "racecar.jpg")
- variant = blob.variant(resize: "100x100", monochrome: true).processed
+ variant = blob.variant(resize_to_fit: [100, 100], monochrome: true).processed
assert_match(/racecar\.jpg/, variant.service_url)
image = read_image(variant)
@@ -26,17 +26,24 @@ class ActiveStorage::VariantTest < ActiveSupport::TestCase
end
test "center-weighted crop of JPEG blob" do
- blob = create_file_blob(filename: "racecar.jpg")
- variant = blob.variant(combine_options: {
- gravity: "center",
- resize: "100x100^",
- crop: "100x100+0+0",
- }).processed
- assert_match(/racecar\.jpg/, variant.service_url)
+ begin
+ ActiveStorage.processor = nil
+ blob = create_file_blob(filename: "racecar.jpg")
+ variant = ActiveSupport::Deprecation.silence do
+ blob.variant(combine_options: {
+ gravity: "center",
+ resize: "100x100^",
+ crop: "100x100+0+0",
+ }).processed
+ end
+ assert_match(/racecar\.jpg/, variant.service_url)
- image = read_image(variant)
- assert_equal 100, image.width
- assert_equal 100, image.height
+ image = read_image(variant)
+ assert_equal 100, image.width
+ assert_equal 100, image.height
+ ensure
+ ActiveStorage.processor = :mini_magick
+ end
end
test "resized variation of PSD blob" do
@@ -80,4 +87,20 @@ class ActiveStorage::VariantTest < ActiveSupport::TestCase
variant = blob.variant(font: "a" * 10_000).processed
assert_operator variant.service_url.length, :<, 525
end
+
+ test "works for vips processor" do
+ begin
+ ActiveStorage.processor = :vips
+ blob = create_file_blob(filename: "racecar.jpg")
+ variant = blob.variant(thumbnail_image: 100).processed
+
+ image = read_image(variant)
+ assert_equal 100, image.width
+ assert_equal 67, image.height
+ rescue LoadError
+ # libvips not installed
+ ensure
+ ActiveStorage.processor = :mini_magick
+ end
+ end
end
diff --git a/activestorage/test/test_helper.rb b/activestorage/test/test_helper.rb
index 028874f374..4787eccd09 100644
--- a/activestorage/test/test_helper.rb
+++ b/activestorage/test/test_helper.rb
@@ -7,7 +7,7 @@ require "bundler/setup"
require "active_support"
require "active_support/test_case"
require "active_support/testing/autorun"
-require "mini_magick"
+require "image_processing/mini_magick"
begin
require "byebug"