├── vendor
├── cmyk.icm
└── sRGB_v4_ICC_preference.icc
├── samples
├── sample.gif
├── sample.jpg
├── sample.pdf
├── sample.png
├── sample.tif
├── sample_anim.gif
├── sample_cmyk.jpg
├── white pixel.png
├── landscape_sample.png
└── sample.svg
├── lib
├── dragonfly_libvips
│ ├── version.rb
│ ├── analysers
│ │ └── image_properties.rb
│ ├── plugin.rb
│ ├── processors
│ │ ├── rotate.rb
│ │ ├── extract_area.rb
│ │ ├── encode.rb
│ │ └── thumb.rb
│ └── dimensions.rb
└── dragonfly_libvips.rb
├── Gemfile
├── .gitignore
├── bin
├── setup
└── console
├── Rakefile
├── Guardfile
├── test
├── support
│ └── image_assertions.rb
├── dragonfly_libvips
│ ├── analysers
│ │ └── image_properties_test.rb
│ ├── processors
│ │ ├── extract_area_test.rb
│ │ ├── rotate_test.rb
│ │ ├── encode_test.rb
│ │ └── thumb_test.rb
│ ├── plugin_test.rb
│ └── dimensions_test.rb
└── test_helper.rb
├── .travis.yml
├── LICENSE.txt
├── dragonfly_libvips.gemspec
├── CHANGELOG.md
└── README.md
/vendor/cmyk.icm:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/did/dragonfly_libvips/master/vendor/cmyk.icm
--------------------------------------------------------------------------------
/samples/sample.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/did/dragonfly_libvips/master/samples/sample.gif
--------------------------------------------------------------------------------
/samples/sample.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/did/dragonfly_libvips/master/samples/sample.jpg
--------------------------------------------------------------------------------
/samples/sample.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/did/dragonfly_libvips/master/samples/sample.pdf
--------------------------------------------------------------------------------
/samples/sample.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/did/dragonfly_libvips/master/samples/sample.png
--------------------------------------------------------------------------------
/samples/sample.tif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/did/dragonfly_libvips/master/samples/sample.tif
--------------------------------------------------------------------------------
/lib/dragonfly_libvips/version.rb:
--------------------------------------------------------------------------------
1 | module DragonflyLibvips
2 | VERSION = '2.3.3'.freeze
3 | end
4 |
--------------------------------------------------------------------------------
/samples/sample_anim.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/did/dragonfly_libvips/master/samples/sample_anim.gif
--------------------------------------------------------------------------------
/samples/sample_cmyk.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/did/dragonfly_libvips/master/samples/sample_cmyk.jpg
--------------------------------------------------------------------------------
/samples/white pixel.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/did/dragonfly_libvips/master/samples/white pixel.png
--------------------------------------------------------------------------------
/samples/landscape_sample.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/did/dragonfly_libvips/master/samples/landscape_sample.png
--------------------------------------------------------------------------------
/Gemfile:
--------------------------------------------------------------------------------
1 | source 'https://rubygems.org'
2 |
3 | # Specify your gem's dependencies in dragonfly_libvips.gemspec
4 | gemspec
5 |
--------------------------------------------------------------------------------
/vendor/sRGB_v4_ICC_preference.icc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/did/dragonfly_libvips/master/vendor/sRGB_v4_ICC_preference.icc
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | /.bundle/
2 | /.yardoc
3 | /Gemfile.lock
4 | /_yardoc/
5 | /coverage/
6 | /doc/
7 | /pkg/
8 | /spec/reports/
9 | /tmp/
10 | *.log
--------------------------------------------------------------------------------
/bin/setup:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 | set -euo pipefail
3 | IFS=$'\n\t'
4 | set -vx
5 |
6 | bundle install
7 |
8 | # Do any other automated setup that you need to do here
9 |
--------------------------------------------------------------------------------
/samples/sample.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/Rakefile:
--------------------------------------------------------------------------------
1 | require "bundler/gem_tasks"
2 | require "rake/testtask"
3 |
4 | Rake::TestTask.new(:test) do |t|
5 | t.libs << "test"
6 | t.libs << "lib"
7 | t.test_files = FileList['test/**/*_test.rb']
8 | end
9 |
10 | task :default => :test
11 |
--------------------------------------------------------------------------------
/Guardfile:
--------------------------------------------------------------------------------
1 | # A sample Guardfile
2 | # More info at https://github.com/guard/guard#readme
3 |
4 | guard :minitest do
5 | watch(%r{^lib/(.+)\.rb$}) { |m| "test/#{m[1]}_test.rb" }
6 | watch(%r{^test/.+_test\.rb$})
7 | watch(%r{^test/test_helper\.rb$}) { 'test' }
8 | end
9 |
--------------------------------------------------------------------------------
/bin/console:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 |
3 | require "bundler/setup"
4 | require "dragonfly_libvips"
5 |
6 | # You can add fixtures and/or initialization code here to make experimenting
7 | # with your gem easier. You can also use a different console, if you like.
8 |
9 | # (If you use this, don't forget to add pry to your Gemfile!)
10 | # require "pry"
11 | # Pry.start
12 |
13 | require "irb"
14 | IRB.start
15 |
--------------------------------------------------------------------------------
/test/support/image_assertions.rb:
--------------------------------------------------------------------------------
1 | require 'vips'
2 |
3 | def image_properties(content)
4 |
5 | img = Vips::Image.new_from_file(content.path, access: :sequential)
6 |
7 | {
8 | format: File.extname(img.filename)[1..-1],
9 | width: img.width,
10 | height: img.height,
11 | xres: img.xres,
12 | yres: img.yres,
13 | }
14 | end
15 |
16 | module MiniTest::Assertions
17 | [:width, :height, :format].each do |property|
18 | define_method "assert_#{property}" do |obj, value|
19 | assert_equal value, image_properties(obj)[property]
20 | end
21 | Object.infect_an_assertion "assert_#{property}", "must_have_#{property}", :reverse
22 | end
23 | end
24 |
--------------------------------------------------------------------------------
/test/dragonfly_libvips/analysers/image_properties_test.rb:
--------------------------------------------------------------------------------
1 | require 'test_helper'
2 |
3 | describe DragonflyLibvips::Analysers::ImageProperties do
4 | let(:app) { test_libvips_app }
5 | let(:analyser) { DragonflyLibvips::Analysers::ImageProperties.new }
6 | let(:png) { Dragonfly::Content.new(app, SAMPLES_DIR.join('sample.png')) } # 280x355
7 | let(:jpg) { Dragonfly::Content.new(app, SAMPLES_DIR.join('sample.jpg')) } # 280x355
8 |
9 | it { analyser.call(png).must_equal('format' => 'png', 'width' => 280, 'height' => 355, 'xres' => 72.0, 'yres' => 72.0, 'progressive' => false) }
10 |
11 | describe 'jpgs' do
12 | it { analyser.call(jpg)['progressive'].must_equal false }
13 | end
14 | end
15 |
--------------------------------------------------------------------------------
/test/test_helper.rb:
--------------------------------------------------------------------------------
1 | require 'bundler/setup'
2 |
3 | require 'minitest'
4 | require 'minitest/autorun'
5 | require 'minitest/reporters'
6 | require 'minitest/spec'
7 |
8 | require 'dragonfly'
9 | require 'dragonfly_libvips'
10 |
11 | SAMPLES_DIR = Pathname.new(File.expand_path('../../samples', __FILE__))
12 |
13 | Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each { |f| require f }
14 |
15 | Minitest::Reporters.use! Minitest::Reporters::SpecReporter.new
16 |
17 | def test_app(name = nil)
18 | Dragonfly::App.instance(name).tap do |app|
19 | app.datastore = Dragonfly::MemoryDataStore.new
20 | app.secret = 'test secret'
21 | end
22 | end
23 |
24 | def test_libvips_app
25 | test_app.configure do
26 | plugin :libvips
27 | end
28 | end
29 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | # libvips build instructions taken from https://github.com/marcbachmann/dockerfile-libvips
2 |
3 | language: ruby
4 | cache: bundler
5 | script: 'bundle exec rake'
6 | sudo: required
7 | dist: trusty
8 | rvm:
9 | - 2.6.1
10 |
11 | before_install:
12 | - gem update bundler
13 | - sudo apt-get update
14 | - sudo apt-get install -y gobject-introspection libgirepository1.0-dev libglib2.0-dev libpoppler-glib-dev libgif-dev
15 | - curl -OL https://github.com/libvips/libvips/releases/download/v8.7.4/vips-8.7.4.tar.gz
16 | - tar zxvf vips-8.7.4.tar.gz && cd vips-8.7.4 && ./configure $1 && sudo make && sudo make install
17 | - export GI_TYPELIB_PATH=/usr/local/lib/girepository-1.0/
18 | - sudo ldconfig
19 |
20 | notifications:
21 | email:
22 | recipients:
23 | - tomas.celizna@gmail.com
24 | on_failure: change
25 | on_success: never
26 |
--------------------------------------------------------------------------------
/lib/dragonfly_libvips.rb:
--------------------------------------------------------------------------------
1 | require 'dragonfly'
2 | require 'dragonfly_libvips/dimensions'
3 | require 'dragonfly_libvips/plugin'
4 | require 'dragonfly_libvips/version'
5 | require 'vips'
6 |
7 | module DragonflyLibvips
8 | class UnsupportedFormat < RuntimeError; end
9 | class UnsupportedOutputormat < RuntimeError; end
10 |
11 | CMYK_PROFILE_PATH = File.expand_path('../vendor/cmyk.icm', __dir__)
12 | EPROFILE_PATH = File.expand_path('../vendor/sRGB_v4_ICC_preference.icc', __dir__)
13 |
14 | SUPPORTED_FORMATS = begin
15 | output = `vips -l | grep -i ForeignLoad`
16 | output.scan(/\.(\w{1,4})/).flatten.compact.sort.map(&:downcase).uniq
17 | end
18 |
19 | SUPPORTED_OUTPUT_FORMATS = begin
20 | output = `vips -l | grep -i ForeignSave`
21 | output.scan(/\.(\w{1,4})/).flatten.compact.sort.map(&:downcase).uniq
22 | end - %w[
23 | csv
24 | fit
25 | fits
26 | fts
27 | mat
28 | pbm
29 | pfm
30 | pgm
31 | ppm
32 | v
33 | vips
34 | webp
35 | ]
36 |
37 | FORMATS_WITHOUT_PROFILE_SUPPORT = %w[bmp dz gif hdr webp heic]
38 | end
39 |
--------------------------------------------------------------------------------
/lib/dragonfly_libvips/analysers/image_properties.rb:
--------------------------------------------------------------------------------
1 | require 'vips'
2 |
3 | module DragonflyLibvips
4 | module Analysers
5 | class ImageProperties
6 | DPI = 300
7 |
8 | def call(content)
9 | return {} unless content.ext
10 | return {} unless SUPPORTED_FORMATS.include?(content.ext.downcase)
11 |
12 | input_options = {}
13 | input_options['access'] = 'sequential'
14 | input_options['autorotate'] = true if content.mime_type == 'image/jpeg'
15 | input_options['dpi'] = DPI if content.mime_type == 'application/pdf'
16 |
17 | img = ::Vips::Image.new_from_file(content.path, input_options)
18 |
19 | width = img.width
20 | height = img.height
21 | xres = img.xres
22 | yres = img.yres
23 |
24 | {
25 | 'format' => content.ext.to_s,
26 | 'width' => width,
27 | 'height' => height,
28 | 'xres' => xres,
29 | 'yres' => yres,
30 | 'progressive' => (content.mime_type == 'image/jpeg' && img.get('jpeg-multiscan') != 0)
31 | }
32 | end
33 | end
34 | end
35 | end
36 |
--------------------------------------------------------------------------------
/LICENSE.txt:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2016 Tomas Celizna
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in
13 | all copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21 | THE SOFTWARE.
22 |
--------------------------------------------------------------------------------
/test/dragonfly_libvips/processors/extract_area_test.rb:
--------------------------------------------------------------------------------
1 | require 'test_helper'
2 |
3 | describe DragonflyLibvips::Processors::ExtractArea do
4 | let(:app) { test_libvips_app }
5 | let(:content) { Dragonfly::Content.new(app, SAMPLES_DIR.join('sample.png')) } # 280x355
6 | let(:processor) { DragonflyLibvips::Processors::ExtractArea.new }
7 |
8 | let(:x) { 100 }
9 | let(:y) { 100 }
10 | let(:width) { 100 }
11 | let(:height) { 200 }
12 |
13 | describe 'keep format' do
14 | before { processor.call(content, x, y, width, height) }
15 |
16 | it { content.must_have_width width }
17 | it { content.must_have_height height }
18 | end
19 |
20 | describe 'convert to format' do
21 | before { processor.call(content, x, y, width, height, format: 'jpg') }
22 |
23 | it { content.must_have_width width }
24 | it { content.must_have_height height }
25 | it { content.ext.must_equal 'jpg' }
26 | end
27 |
28 | describe 'tempfile has extension' do
29 | let(:format) { 'jpg' }
30 | before { processor.call(content, x, y, width, height, format: format) }
31 | it { content.tempfile.path.must_match /\.#{format}\z/ }
32 | end
33 | end
34 |
--------------------------------------------------------------------------------
/test/dragonfly_libvips/processors/rotate_test.rb:
--------------------------------------------------------------------------------
1 | require 'test_helper'
2 |
3 | describe DragonflyLibvips::Processors::Rotate do
4 | let(:app) { test_libvips_app }
5 | let(:content) { Dragonfly::Content.new(app, SAMPLES_DIR.join('sample.png')) } # 280x355
6 | let(:processor) { DragonflyLibvips::Processors::Rotate.new }
7 |
8 | describe 'rotate 90' do
9 | before { processor.call(content, 90) }
10 |
11 | it { content.must_have_width 355 }
12 | it { content.must_have_height 280 }
13 | end
14 |
15 | describe 'rotate 180' do
16 | before { processor.call(content, 180) }
17 |
18 | it { content.must_have_width 280 }
19 | it { content.must_have_height 355 }
20 | end
21 |
22 | describe 'rotate 270' do
23 | before { processor.call(content, 270) }
24 |
25 | it { content.must_have_width 355 }
26 | it { content.must_have_height 280 }
27 | end
28 |
29 | describe 'rotate with format' do
30 | before { processor.call(content, 90, format: 'jpg') }
31 |
32 | it { content.must_have_width 355 }
33 | it { content.must_have_height 280 }
34 | it { content.ext.must_equal 'jpg' }
35 | end
36 |
37 | describe 'tempfile has extension' do
38 | let(:format) { 'jpg' }
39 | before { processor.call(content, 90, format: format) }
40 | it { content.tempfile.path.must_match /\.#{format}\z/ }
41 | end
42 | end
43 |
--------------------------------------------------------------------------------
/dragonfly_libvips.gemspec:
--------------------------------------------------------------------------------
1 |
2 | lib = File.expand_path('lib', __dir__)
3 | $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4 | require 'dragonfly_libvips/version'
5 |
6 | Gem::Specification.new do |spec|
7 | spec.name = 'dragonfly_libvips'
8 | spec.version = DragonflyLibvips::VERSION
9 | spec.authors = ['Tomas Celizna']
10 | spec.email = ['tomas.celizna@gmail.com']
11 |
12 | spec.summary = 'Dragonfly analysers and processors for libvips image processing library.'
13 | spec.homepage = 'https://github.com/tomasc/dragonfly_libvips'
14 | spec.license = 'MIT'
15 |
16 | spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
17 | spec.bindir = 'exe'
18 | spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
19 | spec.require_paths = ['lib']
20 |
21 | spec.add_dependency 'dragonfly', '~> 1.0'
22 | spec.add_dependency 'ruby-vips', '~> 2.0', '< 2.0.14'
23 |
24 | spec.add_development_dependency 'bundler', '~> 2.0'
25 | spec.add_development_dependency 'rb-readline'
26 | spec.add_development_dependency 'guard'
27 | spec.add_development_dependency 'guard-minitest'
28 | spec.add_development_dependency 'minitest', '~> 5.0'
29 | spec.add_development_dependency 'minitest-reporters'
30 | spec.add_development_dependency 'rake', '~> 10.0'
31 | end
32 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # CHANGELOG
2 |
3 | ## 2.3.3
4 |
5 | * locks `ruby-vips` to `< 2.0.14`
6 | * adds support for `libvips` `8.8.0`
7 |
8 | ## 2.3.2
9 |
10 | * FIX: support for .gif
11 |
12 | ## 2.3.1
13 |
14 | * improved `SUPPORTED_FORMATS` matching that ignores case
15 |
16 | ## 2.3.0
17 |
18 | * add support for progressive JPEGs
19 |
20 | ## 2.2.0
21 |
22 | * add `SUPPORTED_FORMATS` and `SUPPORTED_OUTPUT_FORMATS` and raise errors when formats are not matching
23 | * add more thorough tests for supported formats
24 | * skip unnecessary conversion from-to same format
25 | * add `extract_area` processor
26 |
27 | ## 2.1.3
28 |
29 | * make sure the downcase analyser survives nil
30 |
31 | ## 2.1.2
32 |
33 | * changed image properties analyser to downcase the image's format
34 |
35 | ## 2.1.1
36 |
37 | * add `CMYK` support using the `cmyk.icm` profile
38 |
39 | ## 2.1.0
40 |
41 | * `thumb` process refactored for `Vips::Image.thumbnail`, with faster performance
42 |
43 | ## 2.0.1
44 |
45 | * `ruby-vips` updated to `>= 2.0.1`
46 | * added `autorotate` support, based on `orientation` value from EXIF tags (JPEG only)
47 |
48 | ## 2.0.0
49 |
50 | * `ruby-vips` updated to `~> 2.0`
51 |
52 | ## 1.0.4
53 |
54 | * `vips` is required closer to when the classes are called, in hope of fixing [#107](https://github.com/jcupitt/ruby-vips/issues/107)
55 |
56 | ## 1.0.0
57 |
58 | * rewritten to use `ruby-vips` instead of CLI vips utils, which should result in better performance
59 | * processors `convert`, `vips` and `vipsthumbnail` have been removed
60 |
--------------------------------------------------------------------------------
/test/dragonfly_libvips/processors/encode_test.rb:
--------------------------------------------------------------------------------
1 | require 'test_helper'
2 |
3 | describe DragonflyLibvips::Processors::Encode do
4 | let(:app) { test_libvips_app }
5 | let(:content_image) { Dragonfly::Content.new(app, SAMPLES_DIR.join('sample.png')) } # 280x355
6 | let(:processor) { DragonflyLibvips::Processors::Encode.new }
7 |
8 | describe 'SUPPORTED_FORMATS' do
9 | DragonflyLibvips::SUPPORTED_FORMATS.each do |format|
10 | unless File.exist?(SAMPLES_DIR.join("sample.#{format}"))
11 | it(format) { skip "sample.#{format} does not exist, skipping" }
12 | next
13 | end
14 |
15 | let(:content) { app.fetch_file SAMPLES_DIR.join("sample.#{format}") }
16 |
17 | DragonflyLibvips::SUPPORTED_OUTPUT_FORMATS.each do |output_format|
18 | it("#{format} to #{output_format}") do
19 | result = content.encode(output_format)
20 | content.encode(output_format).mime_type.must_equal Rack::Mime.mime_type(".#{output_format}")
21 | content.encode(output_format).size.must_be :>, 0
22 | content.encode(output_format).tempfile.path.must_match /\.#{output_format_short(output_format)}\z/
23 | end
24 | end
25 | end
26 | end
27 |
28 | describe 'allows for options' do
29 | before { processor.call(content_image, 'jpg', output_options: { Q: 50 }) }
30 | it { content_image.ext.must_equal 'jpg' }
31 | end
32 |
33 | def output_format_short(format)
34 | case format
35 | when 'tiff' then 'tif'
36 | when 'jpeg' then 'jpg'
37 | else format
38 | end
39 | end
40 | end
41 |
--------------------------------------------------------------------------------
/lib/dragonfly_libvips/plugin.rb:
--------------------------------------------------------------------------------
1 | require 'dragonfly_libvips/analysers/image_properties'
2 | require 'dragonfly_libvips/processors/encode'
3 | require 'dragonfly_libvips/processors/extract_area'
4 | require 'dragonfly_libvips/processors/rotate'
5 | require 'dragonfly_libvips/processors/thumb'
6 |
7 | module DragonflyLibvips
8 | class Plugin
9 | def call(app, _opts = {})
10 | # Analysers
11 | app.add_analyser :image_properties, DragonflyLibvips::Analysers::ImageProperties.new
12 |
13 | %w[ width
14 | height
15 | xres
16 | yres
17 | format
18 | ].each do |name|
19 | app.add_analyser(name) { |c| c.analyse(:image_properties)[name] }
20 | end
21 |
22 | app.add_analyser(:aspect_ratio) { |c| c.analyse(:width).to_f / c.analyse(:height).to_f }
23 | app.add_analyser(:portrait) { |c| c.analyse(:aspect_ratio) < 1.0 }
24 | app.add_analyser(:landscape) { |c| !c.analyse(:portrait) }
25 |
26 | app.add_analyser(:image) do |c|
27 | begin
28 | c.analyse(:image_properties).key?('format')
29 | rescue ::Vips::Error
30 | false
31 | end
32 | end
33 |
34 | # Aliases
35 | app.define(:portrait?) { portrait }
36 | app.define(:landscape?) { landscape }
37 | app.define(:image?) { image }
38 |
39 | # Processors
40 | app.add_processor :encode, Processors::Encode.new
41 | app.add_processor :extract_area, Processors::ExtractArea.new
42 | app.add_processor :thumb, Processors::Thumb.new
43 | app.add_processor :rotate, Processors::Rotate.new
44 | end
45 | end
46 | end
47 |
48 | Dragonfly::App.register_plugin(:libvips) { DragonflyLibvips::Plugin.new }
49 |
--------------------------------------------------------------------------------
/lib/dragonfly_libvips/processors/rotate.rb:
--------------------------------------------------------------------------------
1 | require 'vips'
2 |
3 | module DragonflyLibvips
4 | module Processors
5 | class Rotate
6 | def call(content, rotate, options = {})
7 | raise UnsupportedFormat unless content.ext
8 | raise UnsupportedFormat unless SUPPORTED_FORMATS.include?(content.ext.downcase)
9 |
10 | options = options.each_with_object({}) { |(k, v), memo| memo[k.to_s] = v } # stringify keys
11 | format = options.fetch('format', content.ext)
12 |
13 | input_options = options.fetch('input_options', {})
14 |
15 | # input_options['access'] ||= 'sequential'
16 | if content.mime_type == 'image/jpeg'
17 | input_options['autorotate'] = true unless input_options.has_key?('autorotate')
18 | end
19 |
20 | output_options = options.fetch('output_options', {})
21 | if FORMATS_WITHOUT_PROFILE_SUPPORT.include?(format)
22 | output_options.delete('profile')
23 | else
24 | output_options['profile'] ||= input_options.fetch('profile', EPROFILE_PATH)
25 | end
26 | output_options.delete('Q') unless format.to_s =~ /jpg|jpeg/i
27 | output_options['format'] ||= format.to_s if format.to_s =~ /gif|bmp/i
28 |
29 | img = ::Vips::Image.new_from_file(content.path, input_options)
30 | img = img.rot("d#{rotate}")
31 |
32 | content.update(
33 | img.write_to_buffer(".#{format}", output_options),
34 | 'name' => "temp.#{format}",
35 | 'format' => format
36 | )
37 | content.ext = format
38 | end
39 |
40 | def update_url(url_attributes, _, options = {})
41 | options = options.each_with_object({}) { |(k, v), memo| memo[k.to_s] = v } # stringify keys
42 | return unless format = options.fetch('format', nil)
43 | url_attributes.ext = format
44 | end
45 | end
46 | end
47 | end
48 |
--------------------------------------------------------------------------------
/lib/dragonfly_libvips/processors/extract_area.rb:
--------------------------------------------------------------------------------
1 | require 'vips'
2 |
3 | module DragonflyLibvips
4 | module Processors
5 | class ExtractArea
6 | def call(content, x, y, width, height, options = {})
7 | raise UnsupportedFormat unless content.ext
8 | raise UnsupportedFormat unless SUPPORTED_FORMATS.include?(content.ext.downcase)
9 |
10 | options = options.each_with_object({}) { |(k, v), memo| memo[k.to_s] = v } # stringify keys
11 | format = options.fetch('format', content.ext)
12 |
13 | input_options = options.fetch('input_options', {})
14 |
15 | # input_options['access'] ||= 'sequential'
16 | if content.mime_type == 'image/jpeg'
17 | input_options['autorotate'] = true unless input_options.has_key?('autorotate')
18 | end
19 |
20 | output_options = options.fetch('output_options', {})
21 | if FORMATS_WITHOUT_PROFILE_SUPPORT.include?(format)
22 | output_options.delete('profile')
23 | else
24 | output_options['profile'] ||= input_options.fetch('profile', EPROFILE_PATH)
25 | end
26 | output_options.delete('Q') unless format.to_s =~ /jpg|jpeg/i
27 | output_options['format'] ||= format.to_s if format.to_s =~ /gif|bmp/i
28 |
29 | img = ::Vips::Image.new_from_file(content.path, input_options)
30 | img = img.extract_area(x, y, width, height)
31 |
32 | content.update(
33 | img.write_to_buffer(".#{format}", output_options),
34 | 'name' => "temp.#{format}",
35 | 'format' => format
36 | )
37 | content.ext = format
38 | end
39 |
40 | def update_url(url_attributes, _, _, _, _, options = {})
41 | options = options.each_with_object({}) { |(k, v), memo| memo[k.to_s] = v } # stringify keys
42 | return unless format = options.fetch('format', nil)
43 | url_attributes.ext = format
44 | end
45 | end
46 | end
47 | end
48 |
--------------------------------------------------------------------------------
/lib/dragonfly_libvips/dimensions.rb:
--------------------------------------------------------------------------------
1 | module DragonflyLibvips
2 | class Dimensions < Struct.new(:geometry, :orig_w, :orig_h)
3 | def self.call(*args)
4 | new(*args).call
5 | end
6 |
7 | def call
8 | return OpenStruct.new(width: orig_w, height: orig_h, scale: 1) if do_not_resize_if_image_smaller_than_requested? || do_not_resize_if_image_larger_than_requested?
9 | OpenStruct.new(width: width, height: height, scale: scale)
10 | end
11 |
12 | private
13 |
14 | def width
15 | if landscape?
16 | dimensions_specified_by_width? ? dimensions.width : dimensions.height / aspect_ratio
17 | else
18 | dimensions_specified_by_height? ? dimensions.height / aspect_ratio : dimensions.width
19 | end
20 | end
21 |
22 | def height
23 | if landscape?
24 | dimensions_specified_by_width? ? dimensions.width * aspect_ratio : dimensions.height
25 | else
26 | dimensions_specified_by_height? ? dimensions.height : dimensions.width * aspect_ratio
27 | end
28 | end
29 |
30 | def scale
31 | width.to_f / orig_w.to_f
32 | end
33 |
34 | def dimensions
35 | w, h = geometry.scan(/\A(\d*)x(\d*)/).flatten.map(&:to_f)
36 | OpenStruct.new(width: w, height: h)
37 | end
38 |
39 | def aspect_ratio
40 | orig_h.to_f / orig_w
41 | end
42 |
43 | def dimensions_specified_by_width?
44 | dimensions.width > 0
45 | end
46 |
47 | def dimensions_specified_by_height?
48 | dimensions.height > 0
49 | end
50 |
51 | def landscape?
52 | aspect_ratio <= 1.0
53 | end
54 |
55 | def portrait?
56 | !landscape?
57 | end
58 |
59 | def do_not_resize_if_image_smaller_than_requested?
60 | return false unless geometry.include? '>'
61 | orig_w < width && orig_h < height
62 | end
63 |
64 | def do_not_resize_if_image_larger_than_requested?
65 | return false unless geometry.include? '<'
66 | orig_w > width && orig_h > height
67 | end
68 | end
69 | end
70 |
--------------------------------------------------------------------------------
/lib/dragonfly_libvips/processors/encode.rb:
--------------------------------------------------------------------------------
1 | require 'vips'
2 |
3 | module DragonflyLibvips
4 | module Processors
5 | class Encode
6 | def call(content, format, options = {})
7 | raise UnsupportedFormat unless content.ext
8 | raise UnsupportedFormat unless SUPPORTED_FORMATS.include?(content.ext.downcase)
9 |
10 | format = format.to_s
11 | format = 'tif' if format == 'tiff'
12 | format = 'jpg' if format == 'jpeg'
13 |
14 | raise UnsupportedOutputFormat unless SUPPORTED_OUTPUT_FORMATS.include?(format.downcase)
15 |
16 | if content.mime_type == Rack::Mime.mime_type(".#{format}")
17 | content.ext ||= format
18 | content.meta['format'] = format
19 | return
20 | end
21 |
22 | options = options.each_with_object({}) { |(k, v), memo| memo[k.to_s] = v } # stringify keys
23 |
24 | input_options = options.fetch('input_options', {})
25 | input_options['access'] ||= 'sequential'
26 | if content.mime_type == 'image/jpeg'
27 | input_options['autorotate'] = true unless input_options.has_key?('autorotate')
28 | end
29 |
30 | output_options = options.fetch('output_options', {})
31 | if FORMATS_WITHOUT_PROFILE_SUPPORT.include?(format)
32 | output_options.delete('profile')
33 | else
34 | output_options['profile'] ||= input_options.fetch('profile', EPROFILE_PATH)
35 | end
36 | output_options.delete('Q') unless format.to_s =~ /jpg|jpeg/i
37 | output_options['format'] ||= format.to_s if format.to_s =~ /gif|bmp/i
38 |
39 | img = ::Vips::Image.new_from_file(content.path, input_options)
40 |
41 | content.update(
42 | img.write_to_buffer(".#{format}", output_options),
43 | 'name' => "temp.#{format}",
44 | 'format' => format
45 | )
46 | content.ext = format
47 | end
48 |
49 | def update_url(url_attributes, format, options = {})
50 | url_attributes.ext = format.to_s
51 | end
52 | end
53 | end
54 | end
55 |
--------------------------------------------------------------------------------
/test/dragonfly_libvips/plugin_test.rb:
--------------------------------------------------------------------------------
1 | require 'test_helper'
2 | require 'openssl'
3 |
4 | module DragonflyLibvips
5 | describe Plugin do
6 | let(:app) { test_app.configure_with(:libvips) }
7 | let(:content) { app.fetch_file(SAMPLES_DIR.join('sample.png')) }
8 |
9 | describe 'analysers' do
10 | it { content.width.must_equal 280 }
11 | it { content.height.must_equal 355 }
12 | it { content.aspect_ratio.must_equal (280.0 / 355.0) }
13 | it { content.xres.must_equal 72.0 }
14 | it { content.yres.must_equal 72.0 }
15 |
16 | it { content.must_be :portrait? }
17 | it { content.portrait.must_equal true } # for use with magic attributes
18 | it { content.wont_be :landscape? }
19 | it { content.landscape.must_equal false } # for use with magic attributes
20 |
21 | it { content.format.must_equal 'png' }
22 |
23 | it { content.must_be :image? }
24 | it { content.image.must_equal true } # for use with magic attributes
25 | it { app.create('blah').wont_be :image? }
26 | end
27 |
28 | describe 'processors that change the url' do
29 | before { app.configure { url_format '/:name' } }
30 |
31 | describe 'encode' do
32 | let(:thumb) { content.encode('png') }
33 |
34 | it { thumb.url.must_match(/^\/sample\.png\?.*job=\w+/) }
35 | it { thumb.format.must_equal 'png' }
36 | it { thumb.meta['format'].must_equal 'png' }
37 | end
38 |
39 | describe 'rotate' do
40 | let(:thumb) { content.rotate(90, format: 'png') }
41 |
42 | it { thumb.url.must_match(/^\/sample\.png\?.*job=\w+/) }
43 | it { thumb.format.must_equal 'png' }
44 | it { thumb.meta['format'].must_equal 'png' }
45 | end
46 |
47 | describe 'thumb' do
48 | let(:thumb) { content.thumb('100x', format: 'png') }
49 |
50 | it { thumb.url.must_match(/^\/sample\.png\?.*job=\w+/) }
51 | it { thumb.format.must_equal 'png' }
52 | it { thumb.meta['format'].must_equal 'png' }
53 | end
54 | end
55 |
56 | describe 'other processors' do
57 | describe 'encode' do
58 | it { content.encode('jpg').format.must_equal 'jpg' }
59 | it { content.encode('jpg', output_options: { Q: 1 }).format.must_equal 'jpg' }
60 | it { content.encode('jpg', output_options: { Q: 1 }).size.must_be :<, 65_000 }
61 | end
62 |
63 | describe 'extract_area' do
64 | it { content.extract_area(100, 100, 50, 200, format: 'jpg').format.must_equal 'jpg' }
65 | it { content.extract_area(100, 100, 50, 200, format: 'jpg').width.must_equal 50 }
66 | it { content.extract_area(100, 100, 50, 200, format: 'jpg').height.must_equal 200 }
67 | end
68 |
69 | describe 'rotate' do
70 | it { content.rotate(90).width.must_equal 355 }
71 | it { content.rotate(90).height.must_equal 280 }
72 | end
73 | end
74 | end
75 | end
76 |
--------------------------------------------------------------------------------
/lib/dragonfly_libvips/processors/thumb.rb:
--------------------------------------------------------------------------------
1 | require 'dragonfly_libvips/dimensions'
2 | require 'vips'
3 |
4 | module DragonflyLibvips
5 | module Processors
6 | class Thumb
7 | OPERATORS = '><'.freeze
8 | RESIZE_GEOMETRY = /\A\d*x\d*[#{OPERATORS}]?\z/ # e.g. '300x200>'
9 | DPI = 300
10 |
11 | def call(content, geometry, options = {})
12 | raise UnsupportedFormat unless content.ext
13 | raise UnsupportedFormat unless SUPPORTED_FORMATS.include?(content.ext.downcase)
14 |
15 | options = options.each_with_object({}) { |(k, v), memo| memo[k.to_s] = v } # stringify keys
16 |
17 | filename = content.path
18 | format = options.fetch('format', content.ext).to_s
19 |
20 | input_options = options.fetch('input_options', {})
21 | input_options['access'] = input_options.fetch('access', 'sequential')
22 | input_options['autorotate'] = input_options.fetch('autorotate', true) if content.mime_type == 'image/jpeg'
23 |
24 | if content.mime_type == 'application/pdf'
25 | input_options['dpi'] = input_options.fetch('dpi', DPI)
26 | input_options['page'] = input_options.fetch('page', 0)
27 | else
28 | input_options.delete('page')
29 | input_options.delete('dpi')
30 | end
31 |
32 | output_options = options.fetch('output_options', {})
33 | if FORMATS_WITHOUT_PROFILE_SUPPORT.include?(format)
34 | output_options.delete('profile')
35 | else
36 | output_options['profile'] ||= input_options.fetch('profile', EPROFILE_PATH)
37 | end
38 | output_options.delete('Q') unless format.to_s =~ /jpg|jpeg/i
39 | output_options['format'] ||= format.to_s if format.to_s =~ /gif|bmp/i
40 |
41 | input_options = input_options.each_with_object({}) { |(k, v), memo| memo[k.to_sym] = v } # symbolize
42 | img = ::Vips::Image.new_from_file(filename, input_options)
43 |
44 | dimensions = case geometry
45 | when RESIZE_GEOMETRY then Dimensions.call(geometry, img.width, img.height)
46 | else raise ArgumentError, "Didn't recognise the geometry string: #{geometry}"
47 | end
48 |
49 | thumbnail_options = options.fetch('thumbnail_options', {})
50 | if Vips.at_least_libvips?(8, 8)
51 | thumbnail_options['no_rotate'] = input_options.fetch('no_rotate', false) if content.mime_type == 'image/jpeg'
52 | else
53 | thumbnail_options['auto_rotate'] = input_options.fetch('autorotate', true) if content.mime_type == 'image/jpeg'
54 | end
55 | thumbnail_options['height'] = thumbnail_options.fetch('height', dimensions.height.ceil)
56 | thumbnail_options['import_profile'] = CMYK_PROFILE_PATH if img.get('interpretation') == :cmyk
57 | thumbnail_options['size'] ||= case geometry
58 | when />\z/ then :down # do_not_resize_if_image_smaller_than_requested
59 | when /<\z/ then :up # do_not_resize_if_image_larger_than_requested
60 | else :both
61 | end
62 |
63 | filename += "[page=#{input_options[:page]}]" if content.mime_type == 'application/pdf'
64 |
65 | thumbnail_options = thumbnail_options.each_with_object({}) { |(k, v), memo| memo[k.to_sym] = v } # symbolize
66 | thumb = ::Vips::Image.thumbnail(filename, dimensions.width.ceil, thumbnail_options)
67 |
68 | content.update(
69 | thumb.write_to_buffer(".#{format}", output_options),
70 | 'name' => "temp.#{format}",
71 | 'format' => format
72 | )
73 | content.ext = format
74 | end
75 |
76 | def update_url(url_attributes, _, options = {})
77 | options = options.each_with_object({}) { |(k, v), memo| memo[k.to_s] = v } # stringify keys
78 | return unless format = options.fetch('format', nil)
79 | url_attributes.ext = format
80 | end
81 | end
82 | end
83 | end
84 |
--------------------------------------------------------------------------------
/test/dragonfly_libvips/dimensions_test.rb:
--------------------------------------------------------------------------------
1 | require 'test_helper'
2 |
3 | describe DragonflyLibvips::Dimensions do
4 | let(:geometry) { '' }
5 | let(:orig_w) { nil }
6 | let(:orig_h) { nil }
7 | let(:result) { DragonflyLibvips::Dimensions.call(geometry, orig_w, orig_h) }
8 |
9 | describe 'NNxNN' do
10 | let(:geometry) { '250x250' }
11 |
12 | describe 'when square' do
13 | let(:orig_w) { 1000 }
14 | let(:orig_h) { 1000 }
15 |
16 | it { result.width.must_equal 250 }
17 | it { result.height.must_equal 250 }
18 | it { result.scale.must_equal 250.0 / orig_w }
19 |
20 | describe '250x250>' do
21 | let(:geometry) { '250x250>' }
22 |
23 | describe 'when image larger than specified' do
24 | it 'resize' do
25 | result.width.must_equal 250
26 | result.height.must_equal 250
27 | result.scale.must_equal 250.0 / orig_w
28 | end
29 | end
30 |
31 | describe 'when image smaller than specified' do
32 | let(:orig_w) { 100 }
33 | let(:orig_h) { 100 }
34 | it 'do not resize' do
35 | result.width.must_equal 100
36 | result.height.must_equal 100
37 | result.scale.must_equal 100.0 / orig_w
38 | end
39 | end
40 | end
41 |
42 | describe '250x50<' do
43 | let(:geometry) { '250x250<' }
44 |
45 | describe 'when image larger than specified' do
46 | it 'do not resize' do
47 | result.width.must_equal 1000
48 | result.height.must_equal 1000
49 | result.scale.must_equal 1000.0 / orig_w
50 | end
51 | end
52 |
53 | describe 'when image smaller than specified' do
54 | let(:orig_w) { 100 }
55 | let(:orig_h) { 100 }
56 |
57 | it 'do resize' do
58 | result.width.must_equal 250
59 | result.height.must_equal 250
60 | result.scale.must_equal 250.0 / orig_w
61 | end
62 | end
63 | end
64 | end
65 |
66 | describe 'when landscape' do
67 | let(:orig_w) { 1000 }
68 | let(:orig_h) { 500 }
69 |
70 | it { result.width.must_equal 250 }
71 | it { result.height.must_equal 125 }
72 | it { result.scale.must_equal 250.0 / orig_w }
73 | end
74 |
75 | describe 'when portrait' do
76 | let(:orig_w) { 500 }
77 | let(:orig_h) { 1000 }
78 |
79 | it { result.width.must_equal 125 }
80 | it { result.height.must_equal 250 }
81 | it { result.scale.must_equal 125.0 / orig_w }
82 | end
83 | end
84 |
85 | describe 'NNx' do
86 | let(:geometry) { '250x' }
87 |
88 | describe 'when square' do
89 | let(:orig_w) { 1000 }
90 | let(:orig_h) { 1000 }
91 |
92 | it { result.width.must_equal 250 }
93 | it { result.height.must_equal 250 }
94 | it { result.scale.must_equal 250.0 / orig_w }
95 | end
96 |
97 | describe 'when landscape' do
98 | let(:orig_w) { 1000 }
99 | let(:orig_h) { 500 }
100 |
101 | it { result.width.must_equal 250 }
102 | it { result.height.must_equal 125 }
103 | it { result.scale.must_equal 250.0 / orig_w }
104 | end
105 |
106 | describe 'when portrait' do
107 | let(:orig_w) { 500 }
108 | let(:orig_h) { 1000 }
109 |
110 | it { result.width.must_equal 250 }
111 | it { result.height.must_equal 500 }
112 | it { result.scale.must_equal 250.0 / orig_w }
113 | end
114 | end
115 |
116 | describe 'xNN' do
117 | let(:geometry) { 'x250' }
118 |
119 | describe 'when square' do
120 | let(:orig_w) { 1000 }
121 | let(:orig_h) { 1000 }
122 |
123 | it { result.width.must_equal 250 }
124 | it { result.height.must_equal 250 }
125 | it { result.scale.must_equal 250.0 / orig_w }
126 | end
127 |
128 | describe 'when landscape' do
129 | let(:orig_w) { 1000 }
130 | let(:orig_h) { 500 }
131 |
132 | it { result.width.must_equal 500 }
133 | it { result.height.must_equal 250 }
134 | it { result.scale.must_equal 500.0 / orig_w }
135 | end
136 |
137 | describe 'when portrait' do
138 | let(:orig_w) { 500 }
139 | let(:orig_h) { 1000 }
140 |
141 | it { result.width.must_equal 125 }
142 | it { result.height.must_equal 250 }
143 | it { result.scale.must_equal 125.0 / orig_w }
144 | end
145 | end
146 | end
147 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Dragonfly libvips
2 |
3 | [](https://travis-ci.org/tomasc/dragonfly_libvips) [](http://badge.fury.io/rb/dragonfly_libvips) [](https://coveralls.io/r/tomasc/dragonfly_libvips)
4 |
5 | Dragonfly analysers and processors for [libvips](https://github.com/jcupitt/libvips) image processing library
6 |
7 | From the libvips README:
8 |
9 | > libvips is a 2D image processing library. Compared to similar libraries, [libvips runs quickly and uses little memory](http://www.vips.ecs.soton.ac.uk/index.php?title=Speed_and_Memory_Use). libvips is licensed under the LGPL 2.1+.
10 |
11 | ## Installation
12 |
13 | Add this line to your application's Gemfile:
14 |
15 | ```ruby
16 | gem 'dragonfly_libvips'
17 | ```
18 |
19 | And then execute:
20 |
21 | ```
22 | $ bundle
23 | ```
24 |
25 | Or install it yourself as:
26 |
27 | ```
28 | $ gem install dragonfly_libvips
29 | ```
30 |
31 | ### libvips
32 |
33 | If you run into trouble installing `libvips` with Ruby introspection on Linux, follow the [build steps here](https://github.com/tomasc/dragonfly_libvips/blob/master/.travis.yml). Please note the importance of `gobject-introspection` and `libgirepository1.0-dev` plus the `export GI_TYPELIB_PATH=/usr/local/lib/girepository-1.0/` and `ldconfig`.
34 |
35 | ## Dependencies
36 |
37 | The [vips](http://www.vips.ecs.soton.ac.uk/index.php?title=Supported) library and its [dependencies](https://github.com/jcupitt/libvips#dependencies).
38 |
39 | ## Usage
40 |
41 | Configure your app the usual way:
42 |
43 | ```ruby
44 | Dragonfly.app.configure do
45 | plugin :libvips
46 | end
47 | ```
48 |
49 | ## Supported Formats
50 |
51 | List of supported formats (based on your build and version of the `libvips` library) is available as:
52 |
53 | ```ruby
54 | DragonflyLibvips::SUPPORTED_FORMATS # => ["csv", "dz", "gif", …]
55 | DragonflyLibvips::SUPPORTED_OUTPUT_FORMATS # => ["csv", "dz", "gif", …]
56 | ```
57 |
58 | ## Processors
59 |
60 | ### Thumb
61 |
62 | Create a thumbnail by resizing/cropping
63 |
64 | ```ruby
65 | image.thumb('40x30')
66 | ```
67 |
68 | Below are some examples of geometry strings for `thumb`:
69 |
70 | ```ruby
71 | '400x300' # resize, maintain aspect ratio
72 | '400x' # resize width, maintain aspect ratio
73 | 'x300' # resize height, maintain aspect ratio
74 | '400x300<' # resize only if the image is smaller than this
75 | '400x300>' # resize only if the image is larger than this
76 | ```
77 |
78 | ### Encode
79 |
80 | Change the encoding with
81 |
82 | ```ruby
83 | image.encode('jpg')
84 | ```
85 |
86 | ### Extract Area
87 |
88 | Extract an area from an image.
89 |
90 | ```ruby
91 | image.extract_area(x, y, width, height)
92 | ```
93 |
94 | ### Rotate
95 |
96 | Rotate a number of degrees with
97 |
98 | ```ruby
99 | image.rotate(90)
100 | ```
101 |
102 | ### Options
103 |
104 | All processors support `input_options` and `output_options` for passing additional options to vips. For example:
105 |
106 | ```ruby
107 | image.encode('jpg', output_options: { Q: 50 })
108 | image.encode('jpg', output_options: { interlace: true }) # use interlace to generate a progressive jpg
109 | pdf.encode('jpg', input_options: { page: 0, dpi: 600 })
110 | ```
111 |
112 | Defaults:
113 |
114 | ```ruby
115 | input_options: { access: :sequential }
116 | output_options: { profile: … } # embeds 'sRGB_v4_ICC_preference.icc' profile included with this gem
117 | ```
118 |
119 | ## Analysers
120 |
121 | The following methods are provided
122 |
123 | ```ruby
124 | image.width # => 280
125 | image.height # => 355
126 | image.xres # => 72.0
127 | image.yres # => 72.0
128 | image.progressive # => true
129 | image.aspect_ratio # => 0.788732394366197
130 | image.portrait? # => true
131 | image.landscape? # => false
132 | image.format # => 'png'
133 | image.image? # => true
134 | ```
135 |
136 | ## Development
137 |
138 | After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
139 |
140 | To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
141 |
142 | ## Contributing
143 |
144 | Bug reports and pull requests are welcome on GitHub at .
145 |
146 | ## License
147 |
148 | The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
149 |
--------------------------------------------------------------------------------
/test/dragonfly_libvips/processors/thumb_test.rb:
--------------------------------------------------------------------------------
1 | require 'test_helper'
2 | require 'ostruct'
3 |
4 | describe DragonflyLibvips::Processors::Thumb do
5 | let(:app) { test_libvips_app }
6 | let(:image) { Dragonfly::Content.new(app, SAMPLES_DIR.join('sample.png')) } # 280x355
7 | let(:pdf) { Dragonfly::Content.new(app, SAMPLES_DIR.join('sample.pdf')) }
8 | let(:jpg) { Dragonfly::Content.new(app, SAMPLES_DIR.join('sample.jpg')) }
9 | let(:cmyk) { Dragonfly::Content.new(app, SAMPLES_DIR.join('sample_cmyk.jpg')) }
10 | let(:gif) { Dragonfly::Content.new(app, SAMPLES_DIR.join('sample.gif')) }
11 | let(:anim_gif) { Dragonfly::Content.new(app, SAMPLES_DIR.join('sample_anim.gif')) }
12 | let(:landscape_image) { Dragonfly::Content.new(app, SAMPLES_DIR.join('landscape_sample.png')) } # 355x280
13 | let(:processor) { DragonflyLibvips::Processors::Thumb.new }
14 |
15 | it 'raises an error if an unrecognized string is given' do
16 | assert_raises(ArgumentError) do
17 | processor.call(image, '30x40#ne!')
18 | end
19 | end
20 |
21 | describe 'cmyk images' do
22 | before { processor.call(cmyk, '30x') }
23 | it { cmyk.must_have_width 30 }
24 | end
25 |
26 | describe 'resizing' do
27 | describe 'xNN' do
28 | before { processor.call(landscape_image, 'x30') }
29 | it { landscape_image.must_have_width 38 }
30 | it { landscape_image.must_have_height 30 }
31 | end
32 |
33 | describe 'NNx' do
34 | before { processor.call(image, '30x') }
35 | it { image.must_have_width 30 }
36 | it { image.must_have_height 38 }
37 | end
38 |
39 | describe 'NNxNN' do
40 | before { processor.call(image, '30x30') }
41 | it { image.must_have_width 24 }
42 | it { image.must_have_height 30 }
43 | end
44 |
45 | describe 'NNxNN>' do
46 | describe 'if the image is smaller than specified' do
47 | before { processor.call(image, '1000x1000>') }
48 | it { image.must_have_width 280 }
49 | it { image.must_have_height 355 }
50 | end
51 |
52 | describe 'if the image is larger than specified' do
53 | before { processor.call(image, '30x30>') }
54 | it { image.must_have_width 24 }
55 | it { image.must_have_height 30 }
56 | end
57 | end
58 |
59 | describe 'NNxNN<' do
60 | describe 'if the image is larger than specified' do
61 | before { processor.call(image, '10x10<') }
62 | it { image.must_have_width 280 }
63 | it { image.must_have_height 355 }
64 | end
65 |
66 | describe 'if the image is smaller than specified' do
67 | before { processor.call(image, '500x500<') }
68 | it { image.must_have_width 394 }
69 | it { image.must_have_height 500 }
70 | end
71 | end
72 | end
73 |
74 | describe 'pdf' do
75 | describe 'resize' do
76 | before { processor.call(pdf, '500x500', format: 'jpg') }
77 | # it { pdf.must_have_width 386 }
78 | it { pdf.must_have_height 500 }
79 | end
80 |
81 | describe 'page param' do
82 | before { processor.call(pdf, '500x500', format: 'jpg', input_options: { page: 0 }) }
83 | # it { pdf.must_have_width 386 }
84 | it { pdf.must_have_height 500 }
85 | end
86 | end
87 |
88 | describe 'jpg' do
89 | describe 'progressive' do
90 | before { processor.call(jpg, '300x', output_options: { interlace: true }) }
91 | it { (`vipsheader -f jpeg-multiscan #{jpg.file.path}`.to_i == 1).must_equal true }
92 | end
93 | end
94 |
95 | describe 'gif' do
96 | describe 'static' do
97 | before { processor.call(gif, '200x') }
98 | it { gif.must_have_width 200 }
99 | end
100 |
101 | describe 'animated' do
102 | before { processor.call(anim_gif, '200x') }
103 | it {
104 | skip 'waiting for full support'
105 | gif.must_have_width 200
106 | }
107 | end
108 | end
109 |
110 | describe 'format' do
111 | let(:url_attributes) { OpenStruct.new }
112 |
113 | describe 'when format passed in' do
114 | before { processor.call(image, '2x2', format: 'jpeg', output_options: { Q: 50 }) }
115 | it { image.ext.must_equal 'jpeg' }
116 | it { image.size.must_be :<, 65_000 }
117 | end
118 |
119 | describe 'when format not passed in' do
120 | before { processor.call(image, '2x2') }
121 | it { image.ext.must_equal 'png' }
122 | end
123 |
124 | describe 'when ext passed in' do
125 | before { processor.update_url(url_attributes, '2x2', 'format' => 'png') }
126 | it { url_attributes.ext.must_equal 'png' }
127 | end
128 |
129 | describe 'when ext not passed in' do
130 | before { processor.update_url(url_attributes, '2x2') }
131 | it { url_attributes.ext.must_be_nil }
132 | end
133 | end
134 |
135 | describe 'tempfile has extension' do
136 | let(:format) { 'jpg' }
137 | before { processor.call(image, '100x', format: 'jpg') }
138 | it { image.tempfile.path.must_match /\.#{format}\z/ }
139 | end
140 | end
141 |
--------------------------------------------------------------------------------