From acf28b1459efa02cd21a660b9de579976f5e870f Mon Sep 17 00:00:00 2001 From: Thomas von Deyen Date: Tue, 25 May 2021 10:35:10 +0200 Subject: [PATCH] Add crop_resize Dragonfly processor The build in thumb processor does not support to resize the image after it has been cropped. In order to make it work in dragonfly 1.4 we add our own processor. --- alchemy_cms.gemspec | 2 +- app/models/alchemy/picture/transformations.rb | 6 ++-- config/initializers/dragonfly.rb | 5 +++ .../dragonfly/processors/crop_resize.rb | 35 +++++++++++++++++++ .../dragonfly/processors/crop_resize_spec.rb | 23 ++++++++++++ spec/models/alchemy/picture_variant_spec.rb | 2 +- spec/support/dragonfly_test_app.rb | 8 +++++ 7 files changed, 76 insertions(+), 5 deletions(-) create mode 100644 lib/alchemy/dragonfly/processors/crop_resize.rb create mode 100644 spec/libraries/dragonfly/processors/crop_resize_spec.rb create mode 100644 spec/support/dragonfly_test_app.rb diff --git a/alchemy_cms.gemspec b/alchemy_cms.gemspec index 38da98c4aa..41f01ee5d3 100644 --- a/alchemy_cms.gemspec +++ b/alchemy_cms.gemspec @@ -37,7 +37,7 @@ Gem::Specification.new do |gem| gem.add_runtime_dependency "awesome_nested_set", ["~> 3.1"] gem.add_runtime_dependency "cancancan", [">= 2.1", "< 4.0"] gem.add_runtime_dependency "coffee-rails", [">= 4.0", "< 6.0"] - gem.add_runtime_dependency "dragonfly", ["~> 1.0", ">= 1.0.7"] + gem.add_runtime_dependency "dragonfly", ["~> 1.4"] gem.add_runtime_dependency "dragonfly_svg", ["~> 0.0.4"] gem.add_runtime_dependency "gutentag", ["~> 2.2", ">= 2.2.1"] gem.add_runtime_dependency "handlebars_assets", ["~> 0.23"] diff --git a/app/models/alchemy/picture/transformations.rb b/app/models/alchemy/picture/transformations.rb index fcabb0b164..72a0fed8b3 100644 --- a/app/models/alchemy/picture/transformations.rb +++ b/app/models/alchemy/picture/transformations.rb @@ -206,12 +206,12 @@ def center_crop(dimensions, upsample) # Use imagemagick to custom crop an image. Uses -thumbnail for better performance when resizing. # def xy_crop_resize(dimensions, top_left, crop_dimensions, upsample) - crop_argument = "-crop #{dimensions_to_string(crop_dimensions)}" + crop_argument = dimensions_to_string(crop_dimensions) crop_argument += "+#{top_left[:x]}+#{top_left[:y]}" - resize_argument = "-resize #{dimensions_to_string(dimensions)}" + resize_argument = dimensions_to_string(dimensions) resize_argument += ">" unless upsample - image_file.convert "#{crop_argument} #{resize_argument}" + image_file.crop_resize(crop_argument, resize_argument) end # Used when centercropping. diff --git a/config/initializers/dragonfly.rb b/config/initializers/dragonfly.rb index 3c4cd6aeb5..b5f28a47fc 100644 --- a/config/initializers/dragonfly.rb +++ b/config/initializers/dragonfly.rb @@ -1,5 +1,6 @@ # frozen_string_literal: true require "dragonfly_svg" +require "alchemy/dragonfly/processors/crop_resize" # Logger Dragonfly.logger = Rails.logger @@ -12,3 +13,7 @@ # Dragonfly 1.4.0 only allows `quality` as argument to `encode` Dragonfly::ImageMagick::Processors::Encode::WHITELISTED_ARGS << "flatten" + +Rails.application.config.after_initialize do + Dragonfly.app(:alchemy_pictures).add_processor(:crop_resize, Alchemy::Dragonfly::Processors::CropResize.new) +end diff --git a/lib/alchemy/dragonfly/processors/crop_resize.rb b/lib/alchemy/dragonfly/processors/crop_resize.rb new file mode 100644 index 0000000000..be664ddf9d --- /dev/null +++ b/lib/alchemy/dragonfly/processors/crop_resize.rb @@ -0,0 +1,35 @@ +# frozen_string_literal: true + +require "dragonfly/image_magick/commands" + +module Alchemy + module Dragonfly + module Processors + class CropResize + include ::Dragonfly::ParamValidators + + IS_CROP_ARGUMENT = ->(args_string) { + args_string.match?(::Dragonfly::ImageMagick::Processors::Thumb::CROP_GEOMETRY) + } + + IS_RESIZE_ARGUMENT = ->(args_string) { + args_string.match?(::Dragonfly::ImageMagick::Processors::Thumb::RESIZE_GEOMETRY) + } + + def call(content, crop_argument, resize_argument) + validate!(crop_argument, &IS_CROP_ARGUMENT) + validate!(resize_argument, &IS_RESIZE_ARGUMENT) + ::Dragonfly::ImageMagick::Commands.convert( + content, + "-crop #{crop_argument} -resize #{resize_argument}" + ) + end + + def update_url(attrs, _args = "", opts = {}) + format = opts["format"] + attrs.ext = format if format + end + end + end + end +end diff --git a/spec/libraries/dragonfly/processors/crop_resize_spec.rb b/spec/libraries/dragonfly/processors/crop_resize_spec.rb new file mode 100644 index 0000000000..ff61fd9a5e --- /dev/null +++ b/spec/libraries/dragonfly/processors/crop_resize_spec.rb @@ -0,0 +1,23 @@ +# frozen_string_literal: true + +require "rails_helper" +require_relative "../../../support/dragonfly_test_app" + +RSpec.describe Alchemy::Dragonfly::Processors::CropResize do + let(:app) { dragonfly_test_app } + let(:file) { Pathname.new(File.expand_path("../../../fixtures/80x60.png", __dir__)) } + let(:image) { Dragonfly::Content.new(app, file) } + let(:processor) { described_class.new } + + it "validates bad crop and resize arguments" do + expect { + processor.call(image, "h4ck", "m3") + }.to raise_error(Dragonfly::ParamValidators::InvalidParameter) + end + + it "works with correct crop and resize arguments" do + expect { + processor.call(image, "4x4+0+0", "20x20>") + }.to_not raise_error + end +end diff --git a/spec/models/alchemy/picture_variant_spec.rb b/spec/models/alchemy/picture_variant_spec.rb index 123ce9a78d..b042f679f0 100644 --- a/spec/models/alchemy/picture_variant_spec.rb +++ b/spec/models/alchemy/picture_variant_spec.rb @@ -70,7 +70,7 @@ end it "crops and resizes the picture" do - expect(subject.steps[0].arguments).to eq(["-crop 123x44+0+0 -resize 160x120>"]) + expect(subject.steps[0].arguments).to eq(["123x44+0+0", "160x120>"]) end end end diff --git a/spec/support/dragonfly_test_app.rb b/spec/support/dragonfly_test_app.rb new file mode 100644 index 0000000000..ddf38f2048 --- /dev/null +++ b/spec/support/dragonfly_test_app.rb @@ -0,0 +1,8 @@ +# frozen_string_literal: true + +def dragonfly_test_app(name = nil) + app = Dragonfly::App.instance(name) + app.datastore = Dragonfly::MemoryDataStore.new + app.secret = "test secret" + app +end