├── .gitignore
├── .rspec
├── .travis.yml
├── Gemfile
├── Gemfile.lock
├── LICENSE
├── README.md
├── Rakefile
├── config.ru
├── cucumber.yml
├── features
├── convert_image_format.feature
├── crop_image.feature
├── define_image_quality.feature
├── resize_image.feature
├── retrieve_image_with_any_name.feature
├── set_background.feature
├── step_definitions
│ └── all_steps.rb
├── support
│ ├── env.rb
│ └── files
│ │ ├── test-cropped_to_300x200.jpg
│ │ ├── test-resized_to_200x.jpg
│ │ ├── test-resized_to_200x200.jpg
│ │ ├── test-resized_to_x200.jpg
│ │ ├── test-with_75%_of_compression.jpg
│ │ ├── test.gif
│ │ ├── test.jpg
│ │ ├── test.png
│ │ ├── with_alpha_channel-with_a_red_background.jpg
│ │ └── with_alpha_channel.png
└── upload_image.feature
├── lib
├── mugshot.rb
└── mugshot
│ ├── application.rb
│ ├── fs_storage.rb
│ ├── http_storage.rb
│ ├── image.rb
│ ├── magick_factory.rb
│ ├── public
│ └── crossdomain.xml
│ ├── s3_storage.rb
│ ├── storage.rb
│ └── version.rb
├── mugshot.gemspec
└── spec
├── files
├── firefox_png.png
├── test-upload.jpg
├── test.jpg
└── test_png.png
├── mugshot
├── application_spec.rb
├── fs_storage_spec.rb
├── http_storage_spec.rb
├── image_spec.rb
├── magick_factory_spec.rb
├── s3_storage_spec.rb
└── storage_spec.rb
├── spec_helper.rb
└── test.html
/.gitignore:
--------------------------------------------------------------------------------
1 | ## MAC OS
2 | .DS_Store
3 |
4 | ## TEXTMATE
5 | *.tmproj
6 | tmtags
7 |
8 | ## EMACS
9 | *~
10 | \#*
11 | .\#*
12 |
13 | ## VIM
14 | *.swp
15 |
16 | ## PROJECT::GENERAL
17 | coverage
18 | rdoc
19 | pkg
20 | nbproject
21 | .bundle
22 | .rvmrc
23 |
24 | ## PROJECT::SPECIFIC
25 | .document
26 |
--------------------------------------------------------------------------------
/.rspec:
--------------------------------------------------------------------------------
1 | --color
2 | --format documentation
3 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | rvm:
2 | - 1.8.7
3 | - 1.9.2
4 | - ree
5 | - rbx
6 | - jruby
--------------------------------------------------------------------------------
/Gemfile:
--------------------------------------------------------------------------------
1 | source "http://rubygems.org"
2 |
3 | # Specify your gem's dependencies in foo.gemspec
4 | gemspec
5 |
--------------------------------------------------------------------------------
/Gemfile.lock:
--------------------------------------------------------------------------------
1 | PATH
2 | remote: .
3 | specs:
4 | mugshot (1.0.0.rc1)
5 | activesupport (>= 2.3.5)
6 | blankslate (>= 2.1.2.3)
7 | fog (>= 0.7.2)
8 | i18n (>= 0.5.0)
9 | rmagick (>= 2.12.2)
10 | sinatra (>= 0.9.4)
11 | uuid (>= 2.0.2)
12 |
13 | GEM
14 | remote: http://rubygems.org/
15 | specs:
16 | activesupport (3.0.9)
17 | blankslate (2.1.2.4)
18 | builder (3.0.0)
19 | cucumber (0.10.2)
20 | builder (>= 2.1.2)
21 | diff-lcs (>= 1.1.2)
22 | gherkin (>= 2.3.5)
23 | json (>= 1.4.6)
24 | term-ansicolor (>= 1.0.5)
25 | diff-lcs (1.1.2)
26 | excon (0.6.5)
27 | fakeweb (1.3.0)
28 | fog (0.10.0)
29 | builder
30 | excon (~> 0.6.5)
31 | formatador (~> 0.2.0)
32 | mime-types
33 | multi_json (~> 1.0.3)
34 | net-scp (~> 1.0.4)
35 | net-ssh (~> 2.1.4)
36 | nokogiri (~> 1.5.0)
37 | ruby-hmac
38 | formatador (0.2.0)
39 | gherkin (2.3.6)
40 | json (>= 1.4.6)
41 | i18n (0.6.0)
42 | json (1.5.1)
43 | macaddr (1.0.0)
44 | mime-types (1.16)
45 | multi_json (1.0.3)
46 | net-scp (1.0.4)
47 | net-ssh (>= 1.99.1)
48 | net-ssh (2.1.4)
49 | nokogiri (1.5.0)
50 | rack (1.2.2)
51 | rack-test (0.5.7)
52 | rack (>= 1.0)
53 | rmagick (2.13.1)
54 | rspec (2.5.0)
55 | rspec-core (~> 2.5.0)
56 | rspec-expectations (~> 2.5.0)
57 | rspec-mocks (~> 2.5.0)
58 | rspec-core (2.5.1)
59 | rspec-expectations (2.5.0)
60 | diff-lcs (~> 1.1.2)
61 | rspec-mocks (2.5.0)
62 | ruby-hmac (0.4.0)
63 | sinatra (1.2.6)
64 | rack (~> 1.1)
65 | tilt (>= 1.2.2, < 2.0)
66 | term-ansicolor (1.0.5)
67 | tilt (1.3.2)
68 | uuid (2.3.3)
69 | macaddr (~> 1.0)
70 |
71 | PLATFORMS
72 | ruby
73 |
74 | DEPENDENCIES
75 | cucumber (>= 0.6.2)
76 | fakeweb
77 | mugshot!
78 | rack-test (>= 0.5.1)
79 | rspec (>= 2.3.0)
80 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2009 Globo.com
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining
4 | a copy of this software and associated documentation files (the
5 | "Software"), to deal in the Software without restriction, including
6 | without limitation the rights to use, copy, modify, merge, publish,
7 | distribute, sublicense, and/or sell copies of the Software, and to
8 | permit persons to whom the Software is furnished to do so, subject to
9 | the following conditions:
10 |
11 | The above copyright notice and this permission notice shall be
12 | included in all copies or substantial portions of the Software.
13 |
14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | Deprecated
2 | ==========
3 |
4 | **Mugshot has been deprecated and is no longer maintained**.
5 |
6 |
7 | Mugshot
8 | =======
9 |
10 | **Mugshot is a dead simple image server**.
11 |
12 |
13 | Overview
14 | --------
15 |
16 | The basic idea of Mugshot is that you upload the largest/highest quality images possible. When retrieving the images you apply different operations to it such as: resizing, rounded corners, transparency and anything else we can think of!
17 |
18 | Only the original image is stored on the server. All operations are performed dynamically, which is why caching is so important (see below).
19 |
20 |
21 | Caching
22 | -------
23 |
24 | Mugshot doesn't cache anything by itself but is designed to play nice with standard HTTP caching.
25 |
26 | For production use, don't even think about using Mugshot without something like Varnish or Squid sitting in front.
27 |
28 |
29 | Using
30 | -----
31 |
32 | Mugshot provides you with a Sinatra application. You can create a **config.ru** file with these contents to start using Mugshot:
33 |
34 |
35 | Using activeresource (3.0.6)
36 | Then you can run it with:
37 |
38 | $ rackup config.ru
39 |
40 | And access in your browser:
41 |
42 | http://localhost:9292/myimg/some-name.jpg
43 |
44 | This would simply return the image located at /tmp/mugshot/myimg, converting it to a JPEG. Additionaly you can pass some operations to be performed over the image:
45 |
46 | http://localhost:9292/resize/100x100/myimg/some-name.jpg # resizing to 100x100 pixels
47 | http://localhost:9292/resize/100x/myimg/some-name.jpg # resizing to 100 pixels in width maintaining aspect ratio
48 | http://localhost:9292/resize/x100/myimg/some-name.jpg # resizing to 100 pixels in height maintaining aspect ratio
49 | http://localhost:9292/crop/200x150/myimg/some-name.jpg # resize and crop image to 200x150
50 | http://localhost:9292/quality/70/crop/200x150/myimg/some-name.jpg # convert it to JPEG with quality of 70% and resize and crop image to 200x150
51 | http://localhost:9292/background/red/crop/200x150/myimg/some-name.jpg # convert it to JPEG with red background and resize and crop image to 200x150
52 |
53 | Supported operations
54 | --------------------
55 |
56 | ### Resize
57 |
58 | /resize/WIDTHxHEIGHT/id/name.jpg (ex: http://mugshot.ws/resize/200x100/myid/thumb.jpg)
59 |
60 | ### Resize keeping aspect ratio
61 |
62 | /resize/WIDTHx/id/name.jpg (ex: http://mugshot.ws/resize/200x/myid/thumb.jpg)
63 |
64 | /resize/xHEIGHT/id/name.jpg (ex: http://mugshot.ws/resize/x100/myid/thumb.jpg)
65 |
66 | ### Crop
67 |
68 | /crop/WIDTHxHEIGHT/id/name.jpg (ex: http://mugshot.ws/crop/200x100/myid/thumb.jpg)
69 |
70 | ### Quality
71 |
72 | /quality/QUALITY/id/name.jpg (ex: http://mugshot.ws/quality/70/myid/thumb.jpg)
73 |
74 | ### Background
75 |
76 | /background/COLOR/id/name.jpg (ex: http://mugshot.ws/background/red/myid/thumb.jpg)
77 |
78 |
79 | Configuration
80 | -------------
81 |
82 | You can further configure your Mugshot::Application when creating it, like so:
83 |
84 | # -*- encoding: utf-8 -*-
85 | require "rubygems"
86 | require "mugshot"
87 |
88 | run Mugshot::Application.new(
89 | :storage => Mugshot::FSStorage.new("/tmp/mugshot"),
90 | :cache_duration => 7.days.to_i, # duration set in cache header (in seconds)
91 | :allowed_sizes => ['640x360', '480x360', '320x240'], # an array with valid sizes for resize and crop operations
92 | :allowed_formats => [:jpg, :png], # an array with the allowed formats
93 | :allowed_names => ['thumb', 'img'], # an array with the allowed names in the URL
94 | :quality_range => 1..100, # the range of allowed values for quality operations
95 | :valid_operations => [:crop, :resize, :quality] # an array with the valid operations
96 | )
97 |
98 | When using the restrictive configurations any value other than the ones allowed will result in a 400 status code being returned. If no restriction is set then any value can be given, which can lead to DOS attacks. Be careful!
99 |
100 | Development
101 | -----------
102 |
103 | Clone the repository and run:
104 |
105 | $ bundle
106 |
107 | This will install all dependencies for you. Then you can run the specs and features:
108 |
109 | $ rake spec
110 | $ rake cucumber
111 |
112 |
--------------------------------------------------------------------------------
/Rakefile:
--------------------------------------------------------------------------------
1 | # -*- encoding: utf-8 -*-
2 | require "rubygems"
3 | require "rake"
4 | require 'rspec/core/rake_task'
5 | require 'cucumber/rake/task'
6 |
7 | task :default => [:spec, :features]
8 |
9 | require 'bundler'
10 | Bundler::GemHelper.install_tasks
11 |
12 | task :default => [:spec, :cucumber]
13 |
14 | RSpec::Core::RakeTask.new :spec
15 | Cucumber::Rake::Task.new(:cucumber)
16 |
--------------------------------------------------------------------------------
/config.ru:
--------------------------------------------------------------------------------
1 | # -*- encoding: utf-8 -*-
2 | $:.unshift(::File.expand_path(::File.join(::File.dirname(__FILE__), "lib")))
3 | require "mugshot"
4 |
5 | system "mkdir -p /tmp/mugshot"
6 | %w[test.jpg with_alpha_channel.png].each do |filename|
7 | system "cp -f features/support/files/#{filename} /tmp/mugshot/#{filename.split('.').first}"
8 | end
9 |
10 | run Mugshot::Application.new(
11 | :storage => Mugshot::FSStorage.new("/tmp/mugshot")
12 | )
13 |
14 |
--------------------------------------------------------------------------------
/cucumber.yml:
--------------------------------------------------------------------------------
1 | default: --guess -r features/support/env.rb -r features/step_definitions
2 |
--------------------------------------------------------------------------------
/features/convert_image_format.feature:
--------------------------------------------------------------------------------
1 | Feature: Convert Image Format
2 |
3 | Scenario: Convert image to gif
4 | Given an image
5 |
6 | When I ask for it with format gif
7 |
8 | Then I should get it with format gif
9 |
10 | Scenario: Convert image to jpg
11 | Given an image
12 |
13 | When I ask for it with format jpg
14 |
15 | Then I should get it with format jpg
16 |
17 | Scenario: Convert image to png
18 | Given an image
19 |
20 | When I ask for it with format png
21 |
22 | Then I should get it with format png
23 |
--------------------------------------------------------------------------------
/features/crop_image.feature:
--------------------------------------------------------------------------------
1 | Feature: Crop image
2 |
3 | Scenario: Crop image
4 | Given an image
5 |
6 | When I ask for it cropped to 300x200
7 |
8 | Then I should get it cropped to 300x200
9 |
--------------------------------------------------------------------------------
/features/define_image_quality.feature:
--------------------------------------------------------------------------------
1 | Feature: Define image quality
2 |
3 | Scenario: Define compression ratio
4 | Given an image
5 |
6 | When I ask for it with 75% of compression
7 |
8 | Then I should get it with 75% of compression
9 |
--------------------------------------------------------------------------------
/features/resize_image.feature:
--------------------------------------------------------------------------------
1 | Feature: Resize image
2 |
3 | Scenario: Resize image
4 | Given an image
5 |
6 | When I ask for it resized to 200x200
7 |
8 | Then I should get it resized to 200x200
9 |
10 | Scenario: Resize image giving only width
11 | Given an image
12 |
13 | When I ask for it resized to 200x
14 |
15 | Then I should get it resized to 200x
16 |
17 | Scenario: Resized image giving only height
18 | Given an image
19 |
20 | When I ask for it resized to x200
21 |
22 | Then I should get it resized to x200
23 |
--------------------------------------------------------------------------------
/features/retrieve_image_with_any_name.feature:
--------------------------------------------------------------------------------
1 | Feature: Retrieve image with any name
2 |
3 | Scenario: Retrieve image with any name
4 | Given an image
5 |
6 | When I ask for it with the name "any_name"
7 | And I ask for it with the name "42"
8 |
9 | Then all of them should be the same
10 |
--------------------------------------------------------------------------------
/features/set_background.feature:
--------------------------------------------------------------------------------
1 | Feature: Set Background
2 |
3 | Scenario: Set background for a png image with alpha channel
4 | Given a png image with alpha channel
5 | When I ask for it with a red background
6 | Then I should get it with a red background
7 |
--------------------------------------------------------------------------------
/features/step_definitions/all_steps.rb:
--------------------------------------------------------------------------------
1 | # -*- encoding: utf-8 -*-
2 | Given /^(a|an) (.*)Storage$/ do |_, storage_name|
3 | case storage_name
4 | when 'FS' then CucumberWorld.storage = Mugshot::FSStorage.new('/tmp/mugshot/cucumber')
5 | when 'HTTP' then CucumberWorld.storage = Mugshot::HTTPStorage.new
6 | end
7 | end
8 |
9 | Given /^an image$/ do
10 | Given "a jpg image test"
11 | end
12 |
13 | Given /^a (.*) image (.*)$/ do |ext, name|
14 | @image = {
15 | :name => name,
16 | :ext => ext,
17 | :filename => "#{name.gsub(' ', '_')}.#{ext}"}
18 |
19 | @image[:id] = write_image(@image[:filename])
20 | end
21 |
22 | When /^I upload an image$/ do
23 | post '/', "file" => Rack::Test::UploadedFile.new("features/support/files/test.jpg", "image/jpeg")
24 | @image_id = last_response.body
25 | end
26 |
27 | When /^I ask for it$/ do
28 | get "/#{@image_id}/any_name.jpg"
29 | @retrieved_image = Magick::Image.from_blob(last_response.body).first
30 | end
31 |
32 | When /^I ask for an image that doesn't exist$/ do
33 | get "/nonexistant_id/any_name.jpg"
34 | end
35 |
36 | When /^I ask for it resized to (.*)$/ do |size|
37 | get "/resize/#{size}/#{@image[:id]}/any_name.jpg"
38 | @retrieved_image = Magick::Image.from_blob(last_response.body).first
39 | end
40 |
41 | When /^I ask for it cropped to (.*)$/ do |size|
42 | get "/crop/#{size}/#{@image[:id]}/any_name.jpg"
43 | @retrieved_image = Magick::Image.from_blob(last_response.body).first
44 | end
45 |
46 | When /^I ask for it with (\d*)% of compression$/ do |compression|
47 | get "/quality/#{compression}/#{@image[:id]}/any_name.jpg"
48 | @retrieved_image = Magick::Image.from_blob(last_response.body).first
49 | end
50 |
51 | When /^I ask for it with format (.*)$/ do |format|
52 | get "/#{@image[:id]}/any_name.#{format}"
53 | @retrieved_image = Magick::Image.from_blob(last_response.body).first
54 | end
55 |
56 | When /^I ask for it with the name "(.*)"$/ do |name|
57 | get "/#{@image[:id]}/#{name}.jpg"
58 | @images_by_name[name] = Magick::Image.from_blob(last_response.body).first
59 | end
60 |
61 | When /^I ask for it with a (.*) background$/ do |color|
62 | get "/background/#{color}/#{@image[:id]}/img.jpg"
63 | @retrieved_image = Magick::Image.from_blob(last_response.body).first
64 | end
65 |
66 | Then /^I should get it$/ do
67 | @retrieved_image.should be_same_image_as("test.jpg")
68 | end
69 |
70 | Then /^I should get a (\d+) response$/ do |response_code|
71 | last_response.status.should == response_code.to_i
72 | end
73 |
74 | Then /^I should get it with format (.*)$/ do |expected_format|
75 | @retrieved_image.should be_same_image_as("test.#{expected_format}")
76 | end
77 |
78 | Then /^I should get it (.*)$/ do |name|
79 | @retrieved_image.should be_same_image_as("#{@image[:name].gsub(' ', '_')}-#{name.underscore.gsub(' ', '_')}.jpg")
80 | end
81 |
82 | Then /^all of them should be the same$/ do
83 | @images_by_name.values.each do |img|
84 | img.should be_same_image_as("test.jpg")
85 | end
86 | end
87 |
--------------------------------------------------------------------------------
/features/support/env.rb:
--------------------------------------------------------------------------------
1 | # -*- encoding: utf-8 -*-
2 | require "rubygems"
3 | $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..'))
4 | $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', '..', 'lib'))
5 | require 'mugshot'
6 |
7 | require 'rack/test'
8 | require 'rspec/expectations'
9 | require 'pp'
10 |
11 | require 'RMagick'
12 |
13 | module CucumberWorld
14 | include RSpec::Matchers
15 | include Rack::Test::Methods
16 |
17 | mattr_accessor :storage
18 |
19 | def app
20 | Mugshot::Application.new(storage)
21 | end
22 |
23 | def write_image(filename)
24 | Mugshot::FSStorage.new('/tmp/mugshot/cucumber').
25 | write(IO.read(File.expand_path(filename, "features/support/files/")))
26 | end
27 | end
28 | World(CucumberWorld)
29 |
30 | Before do
31 | @images_by_name = HashWithIndifferentAccess.new
32 | CucumberWorld.storage = Mugshot::FSStorage.new('/tmp/mugshot/cucumber')
33 | end
34 |
35 | After do
36 | require 'fileutils'
37 | FileUtils.rm_rf("/tmp/mugshot/cucumber")
38 | end
39 |
40 | Rspec::Matchers.define :be_same_image_as do |expected|
41 | match do |actual|
42 | expected = Magick::Image.read(File.expand_path(__FILE__ + "/../files/#{expected}")).first if expected.is_a?(String)
43 |
44 | actual.columns == expected.columns &&
45 | actual.rows == expected.rows &&
46 | actual.difference(expected)[1] < 0.01
47 | end
48 | end
49 |
50 |
--------------------------------------------------------------------------------
/features/support/files/test-cropped_to_300x200.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/globocom/mugshot/914192bac747273b436ca4c35fe445591e30c4e6/features/support/files/test-cropped_to_300x200.jpg
--------------------------------------------------------------------------------
/features/support/files/test-resized_to_200x.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/globocom/mugshot/914192bac747273b436ca4c35fe445591e30c4e6/features/support/files/test-resized_to_200x.jpg
--------------------------------------------------------------------------------
/features/support/files/test-resized_to_200x200.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/globocom/mugshot/914192bac747273b436ca4c35fe445591e30c4e6/features/support/files/test-resized_to_200x200.jpg
--------------------------------------------------------------------------------
/features/support/files/test-resized_to_x200.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/globocom/mugshot/914192bac747273b436ca4c35fe445591e30c4e6/features/support/files/test-resized_to_x200.jpg
--------------------------------------------------------------------------------
/features/support/files/test-with_75%_of_compression.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/globocom/mugshot/914192bac747273b436ca4c35fe445591e30c4e6/features/support/files/test-with_75%_of_compression.jpg
--------------------------------------------------------------------------------
/features/support/files/test.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/globocom/mugshot/914192bac747273b436ca4c35fe445591e30c4e6/features/support/files/test.gif
--------------------------------------------------------------------------------
/features/support/files/test.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/globocom/mugshot/914192bac747273b436ca4c35fe445591e30c4e6/features/support/files/test.jpg
--------------------------------------------------------------------------------
/features/support/files/test.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/globocom/mugshot/914192bac747273b436ca4c35fe445591e30c4e6/features/support/files/test.png
--------------------------------------------------------------------------------
/features/support/files/with_alpha_channel-with_a_red_background.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/globocom/mugshot/914192bac747273b436ca4c35fe445591e30c4e6/features/support/files/with_alpha_channel-with_a_red_background.jpg
--------------------------------------------------------------------------------
/features/support/files/with_alpha_channel.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/globocom/mugshot/914192bac747273b436ca4c35fe445591e30c4e6/features/support/files/with_alpha_channel.png
--------------------------------------------------------------------------------
/features/upload_image.feature:
--------------------------------------------------------------------------------
1 | Feature: Upload Image
2 |
3 | Scenario: Filesystem storage
4 | Given an FSStorage
5 |
6 | When I upload an image
7 | And I ask for it
8 |
9 | Then I should get it
10 |
11 | Scenario: HTTP storage
12 | Given an HTTPStorage
13 |
14 | When I upload an image
15 |
16 | Then I should get a 405 response
17 |
18 | Scenario: Image wasn't uploaded
19 | When I ask for an image that doesn't exist
20 |
21 | Then I should get a 404 response
22 |
--------------------------------------------------------------------------------
/lib/mugshot.rb:
--------------------------------------------------------------------------------
1 | # -*- encoding: utf-8 -*-
2 | require "active_support/core_ext"
3 |
4 | module Mugshot
5 | autoload :MagickFactory, "mugshot/magick_factory"
6 | autoload :Image, "mugshot/image"
7 |
8 | autoload :Storage, "mugshot/storage"
9 | autoload :FSStorage, "mugshot/fs_storage"
10 | autoload :S3Storage, "mugshot/s3_storage"
11 | autoload :HTTPStorage, "mugshot/http_storage"
12 |
13 | autoload :Application, "mugshot/application"
14 | end
15 |
--------------------------------------------------------------------------------
/lib/mugshot/application.rb:
--------------------------------------------------------------------------------
1 | # -*- encoding: utf-8 -*-
2 | require 'sinatra/base'
3 |
4 | class Mugshot::Application < Sinatra::Base
5 |
6 | DEFAULT_VALID_OPERATIONS = %w[background crop resize quality]
7 |
8 | set :static, true
9 | set :public, ::File.expand_path(::File.join(::File.dirname(__FILE__), "public"))
10 |
11 | get '/?' do
12 | 'ok'
13 | end
14 |
15 | post '/?' do
16 | id = @storage.write(params['file'][:tempfile].read)
17 | halt 405 if id.blank?
18 | id
19 | end
20 |
21 | before '/*/?:id/:name.:format' do |splat, id, name, format|
22 | @operations = operations_from_splat(splat)
23 | check_format(format)
24 | check_name(name)
25 | check_operations
26 | end
27 |
28 | get '/*/?:id/:name.:format' do |splat, id, _, format|
29 | image = @storage.read(id)
30 |
31 | if image.blank?
32 | response['Cache-Control'] = "public, max-age=#{1.minute}"
33 | halt 404
34 | end
35 |
36 | begin
37 | process_operations(image, @default_operations)
38 | process_operations(image, @operations)
39 | response['Cache-Control'] = "public, max-age=#{@cache_duration}"
40 | send_image(image, format.to_sym)
41 | ensure
42 | image.destroy!
43 | end
44 | end
45 |
46 | protected
47 |
48 | def initialize(opts)
49 | opts = {:storage => opts} if opts.kind_of?(Mugshot::Storage)
50 | opts.to_options!
51 |
52 | @storage = opts.delete(:storage)
53 | @cache_duration = opts.delete(:cache_duration) || 1.year.to_i
54 | @valid_operations = (opts.delete(:valid_operations) || DEFAULT_VALID_OPERATIONS).map(&:to_s)
55 | @quality_range = opts.delete(:quality_range)
56 | @allowed_sizes = opts.delete(:allowed_sizes)
57 | @allowed_formats = opts.delete(:allowed_formats)
58 | @allowed_names = opts.delete(:allowed_names)
59 |
60 | @default_operations = opts
61 |
62 | super(opts)
63 | end
64 |
65 | private
66 |
67 | def operations_from_splat(splat)
68 | operations = []
69 | begin
70 | operations = Hash[*splat.split('/')]
71 | operations.assert_valid_keys(@valid_operations)
72 | rescue
73 | halt 400
74 | end
75 | operations
76 | end
77 |
78 | def check_format(format)
79 | halt 400 unless valid_format?(format)
80 | end
81 |
82 | def valid_format?(format)
83 | @allowed_formats.blank? || @allowed_formats.map(&:to_s).include?(format)
84 | end
85 |
86 | def check_name(name)
87 | halt 400 unless valid_name?(name)
88 | end
89 |
90 | def valid_name?(name)
91 | @allowed_names.blank? || @allowed_names.map(&:to_s).include?(name)
92 | end
93 |
94 | def check_operations
95 | halt 400 unless valid_quality_operation? && valid_resize_operation? && valid_crop_operation?
96 | end
97 |
98 | def valid_quality_operation?
99 | !@operations.has_key?("quality") || @quality_range.blank? || @quality_range.include?(@operations["quality"].to_i)
100 | end
101 |
102 | def valid_resize_operation?
103 | valid_operation?("resize", @allowed_sizes)
104 | end
105 |
106 | def valid_crop_operation?
107 | valid_operation?("crop", @allowed_sizes)
108 | end
109 |
110 | def valid_operation?(operation, range_or_array)
111 | !@operations.has_key?(operation) || range_or_array.blank? || range_or_array.include?(@operations[operation])
112 | end
113 |
114 | def process_operations(image, operations)
115 | operations.each do |op, op_params|
116 | image.send("#{op}!", op_params.to_s)
117 | end
118 | end
119 |
120 | def send_image(image, format)
121 | content_type format
122 | response['Content-Disposition'] = 'inline'
123 | image.to_blob(:format => format)
124 | end
125 | end
126 |
--------------------------------------------------------------------------------
/lib/mugshot/fs_storage.rb:
--------------------------------------------------------------------------------
1 | # -*- encoding: utf-8 -*-
2 | require 'fileutils'
3 | class Mugshot::FSStorage < Mugshot::Storage
4 | def write(bin)
5 | id = asset_id
6 | path = id_to_path(id)
7 | FileUtils.mkdir_p(File.dirname(File.join(@root_path, path)))
8 | File.open(File.join(@root_path, path), "w") do |fw|
9 | fw.write(bin)
10 | end
11 | id
12 | end
13 |
14 | def read(id)
15 | path = id_to_path(id)
16 | file = File.join(@root_path, path)
17 | return nil unless File.exist? file
18 | Mugshot::Image.new File.open(file)
19 | end
20 |
21 | protected
22 |
23 | def initialize(root_path)
24 | @root_path = root_path
25 | FileUtils.mkdir_p(root_path)
26 | end
27 |
28 | end
29 |
30 |
--------------------------------------------------------------------------------
/lib/mugshot/http_storage.rb:
--------------------------------------------------------------------------------
1 | # -*- encoding: utf-8 -*-
2 | require 'fileutils'
3 | require 'net/http'
4 |
5 | class Mugshot::HTTPStorage < Mugshot::Storage
6 |
7 | def read(id)
8 | url = URI.parse(@url_resolver.call(id))
9 | res = Net::HTTP.start(url.host, url.port) {|http| http.get(url.path)}
10 |
11 | return nil unless res.code.to_i == 200
12 |
13 | Mugshot::Image.new(res.body)
14 | end
15 |
16 | protected
17 |
18 | def initialize(&block)
19 | @url_resolver = block
20 | end
21 |
22 | end
23 |
--------------------------------------------------------------------------------
/lib/mugshot/image.rb:
--------------------------------------------------------------------------------
1 | # -*- encoding: utf-8 -*-
2 | require 'RMagick'
3 | class Mugshot::Image
4 |
5 | def background!(color)
6 | @background_color = color
7 | end
8 |
9 | def resize!(size)
10 | w, h = parse_size(size)
11 | if [w, h].include?(nil)
12 | @image.resize_to_fit! w, h
13 | else
14 | @image.resize! w, h
15 | end
16 |
17 | self
18 | end
19 |
20 | def crop!(size)
21 | w, h = parse_size(size)
22 | @image.resize_to_fill! w, h
23 | self
24 | end
25 |
26 | def quality!(quality)
27 | @quality = quality.to_i
28 | self
29 | end
30 |
31 | def destroy!
32 | @image.destroy!
33 | self
34 | end
35 |
36 | def to_blob(opts = {})
37 | opts.merge!(:quality => @quality)
38 |
39 | set_background(@background_color) if !!@background_color
40 |
41 | @image.strip!
42 | @image.to_blob do
43 | self.format = opts[:format].to_s if opts.include?(:format)
44 | self.quality = opts[:quality] if opts[:quality].present?
45 | end
46 | end
47 |
48 | protected
49 |
50 | def initialize(file_or_blob)
51 | if file_or_blob.is_a?(File)
52 | @image = Magick::Image.read(file_or_blob).first
53 | else
54 | @image = Magick::Image.from_blob(file_or_blob).first
55 | end
56 |
57 | # initialize attrs
58 | @background_color = @quality = nil
59 | end
60 |
61 | private
62 |
63 | def parse_size(size)
64 | size.to_s.split("x").map{|i| i.blank? ? nil : i.to_i}
65 | end
66 |
67 | def set_background(color)
68 | @image = Mugshot::MagickFactory.
69 | create_canvas(@image.columns, @image.rows, color).
70 | composite(@image, Magick::NorthWestGravity, Magick::OverCompositeOp)
71 | end
72 |
73 | end
74 |
--------------------------------------------------------------------------------
/lib/mugshot/magick_factory.rb:
--------------------------------------------------------------------------------
1 | # -*- encoding: utf-8 -*-
2 | require 'RMagick'
3 |
4 | class Mugshot::MagickFactory
5 | def self.create_canvas(columns, rows, color)
6 | Magick::Image.new(columns, rows){ self.background_color = color }
7 | end
8 | end
--------------------------------------------------------------------------------
/lib/mugshot/public/crossdomain.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/lib/mugshot/s3_storage.rb:
--------------------------------------------------------------------------------
1 | # -*- encoding: utf-8 -*-
2 | require 'fog'
3 |
4 | class Mugshot::S3Storage < Mugshot::Storage
5 |
6 | HEADERS = {'x-amz-acl' => 'public-read'}
7 |
8 | def write(bin)
9 | id = asset_id
10 | path = id_to_path(id)
11 | @storage.put_object(@bucket_name, path, bin, HEADERS)
12 | id
13 | end
14 |
15 | def read(id)
16 | path = id_to_path(id)
17 | Mugshot::Image.new(@storage.get_object(@bucket_name, path).body)
18 | rescue Excon::Errors::NotFound
19 | nil
20 | end
21 |
22 | protected
23 |
24 | def initialize(opts)
25 | opts.to_options!
26 | opts.assert_valid_keys(:bucket_name, :access_key_id, :secret_access_key)
27 |
28 | @bucket_name = opts[:bucket_name]
29 |
30 | @storage = Fog::Storage.new(:provider => 'AWS', :aws_access_key_id => opts[:access_key_id], :aws_secret_access_key => opts[:secret_access_key])
31 | @storage.put_bucket(@bucket_name)
32 | end
33 |
34 | end
35 |
36 |
37 |
--------------------------------------------------------------------------------
/lib/mugshot/storage.rb:
--------------------------------------------------------------------------------
1 | # -*- encoding: utf-8 -*-
2 | require 'uuid'
3 | class Mugshot::Storage
4 |
5 | def write(bin)
6 | nil
7 | end
8 |
9 | protected
10 |
11 | def asset_id
12 | UUID.generate :compact
13 | end
14 |
15 | def id_to_path(id)
16 | path = id.to_s.clone
17 | 14.times do |i|
18 | path = path.insert(2 + (i * 2) + i, '/')
19 | end
20 | path
21 | end
22 |
23 | end
24 |
--------------------------------------------------------------------------------
/lib/mugshot/version.rb:
--------------------------------------------------------------------------------
1 | module Mugshot
2 | VERSION = "1.0.0.rc2"
3 | end
4 |
5 |
--------------------------------------------------------------------------------
/mugshot.gemspec:
--------------------------------------------------------------------------------
1 | # encoding: utf-8
2 | $:.push File.expand_path("../lib", __FILE__)
3 | require "mugshot/version"
4 |
5 | Gem::Specification.new do |s|
6 | s.name = "mugshot"
7 | s.version = Mugshot::VERSION
8 | s.authors = ["Caina Nunes", "Fabricio Lopes", "Guilherme Cirne", "Jose Peleteiro", "Vicente Mundim", "Anselmo Alves"]
9 | s.email = ["cainanunes@gmail.com", "fabriciolopesvital@gmail.com", "gcirne@gmail.com", "jose@peleteiro.net", "vicente.mundim@gmail.com", "me@anselmoalves.com"]
10 | s.homepage = %q{http://mugshot.ws}
11 | s.summary = %q{Dead simple image server}
12 | s.description = %q{The basic idea of Mugshot is that you upload the largest/highest quality images possible. When retrieving the images you apply different operations to it such as: resizing, rounded corners, transparency and anything else we can think of!}
13 | s.date = %q{2011-04-25}
14 |
15 | s.rubyforge_project = "mugshot"
16 |
17 | s.rdoc_options = ["--charset=UTF-8"]
18 | s.extra_rdoc_files = ["LICENSE", "README.md"]
19 |
20 | s.files = `git ls-files -- lib/*`.split("\n")
21 | s.test_files = `git ls-files -- {spec,features}/*`.split("\n")
22 | s.require_paths = ["lib"]
23 |
24 | s.add_dependency(%q, [">= 2.3.5"])
25 | s.add_dependency(%q, [">= 0.5.0"])
26 | s.add_dependency(%q, [">= 2.12.2"])
27 | s.add_dependency(%q, [">= 2.0.2"])
28 | s.add_dependency(%q, [">= 2.1.2.3"])
29 | s.add_dependency(%q, [">= 0.9.4"])
30 | s.add_dependency(%q, [">= 0.7.2"])
31 |
32 | s.add_development_dependency(%q, [">= 0"])
33 | s.add_development_dependency(%q, [">= 2.3.0"])
34 | s.add_development_dependency(%q, [">= 0.6.2"])
35 | s.add_development_dependency(%q, [">= 0.5.1"])
36 | end
37 |
--------------------------------------------------------------------------------
/spec/files/firefox_png.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/globocom/mugshot/914192bac747273b436ca4c35fe445591e30c4e6/spec/files/firefox_png.png
--------------------------------------------------------------------------------
/spec/files/test-upload.jpg:
--------------------------------------------------------------------------------
1 | image
2 |
--------------------------------------------------------------------------------
/spec/files/test.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/globocom/mugshot/914192bac747273b436ca4c35fe445591e30c4e6/spec/files/test.jpg
--------------------------------------------------------------------------------
/spec/files/test_png.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/globocom/mugshot/914192bac747273b436ca4c35fe445591e30c4e6/spec/files/test_png.png
--------------------------------------------------------------------------------
/spec/mugshot/application_spec.rb:
--------------------------------------------------------------------------------
1 | # -*- encoding: utf-8 -*-
2 | require File.expand_path(File.dirname(__FILE__) + "/../spec_helper")
3 |
4 | describe Mugshot::Application do
5 | before :each do
6 | @storage = stub(Mugshot::Storage, :kind_of? => true).as_null_object
7 | @image = stub(Mugshot::Image, :blank? => false).as_null_object
8 | @storage.stub!(:read).with("image_id").and_return(@image)
9 |
10 | def app
11 | Mugshot::Application.new(@storage)
12 | end
13 | end
14 |
15 | describe "default values" do
16 | it "should accept default value for quality" do
17 | def app
18 | Mugshot::Application.new(:storage => @storage, :quality => 42)
19 | end
20 |
21 | @image.should_receive(:quality!).with("42")
22 |
23 | get "/image_id/any_name.jpg"
24 | end
25 |
26 | it "should accept default value for background" do
27 | def app
28 | Mugshot::Application.new(:storage => @storage, :background => :blue)
29 | end
30 |
31 | @image.should_receive(:background!).with("blue")
32 |
33 | get "/image_id/any_name.jpg"
34 | end
35 | end
36 |
37 | describe "POST /" do
38 | it "should create image" do
39 | file_read = nil
40 | File.open("spec/files/test-upload.jpg") {|f| file_read = f.read}
41 | @storage.should_receive(:write).with(file_read).and_return("batata")
42 | post "/", "file" => Rack::Test::UploadedFile.new("spec/files/test-upload.jpg", "image/jpeg")
43 |
44 | last_response.status.should == 200
45 | end
46 |
47 | it "should return image id" do
48 | @storage.stub!(:write).and_return("batata")
49 |
50 | post "/", "file" => Rack::Test::UploadedFile.new("spec/files/test.jpg", "image/jpeg")
51 |
52 | last_response.body.should == "batata"
53 | end
54 |
55 | it "should halt 405 when storage doesn't allow writing to" do
56 | @storage.stub!(:write).and_return(nil)
57 |
58 | post "/", "file" => Rack::Test::UploadedFile.new("spec/files/test.jpg", "image/jpeg")
59 |
60 | last_response.status.should == 405
61 | end
62 | end
63 |
64 | describe "GET /:ops/:ops_params/:id/:name.:format" do
65 | it "should perform operations on image" do
66 | @image.should_receive(:resize!).with("140x140")
67 | @image.should_receive(:crop!).with("140x105")
68 | @image.should_receive(:quality!).with("70")
69 | @image.should_receive(:background!).with("red")
70 |
71 | get "/background/red/resize/140x140/crop/140x105/quality/70/image_id/any_name.jpg"
72 | end
73 |
74 | it "should return image" do
75 | @image.stub!(:to_blob).and_return("image data")
76 |
77 | get "/crop/140x105/image_id/any_name.jpg"
78 |
79 | last_response.should be_ok
80 | last_response.body.should == "image data"
81 | end
82 |
83 | it "should destroy image" do
84 | @image.should_receive(:destroy!)
85 | get "/image_id/any_name.jpg"
86 | end
87 |
88 | it "should halt 404 when image doesn't exist" do
89 | @storage.stub!(:read).with("image_id").and_return(nil)
90 |
91 | get "/crop/140x105/image_id/any_name.jpg"
92 |
93 | last_response.should be_not_found
94 | last_response.body.should be_empty
95 | end
96 |
97 | it "should halt 400 on operations that are not allowed" do
98 | @image.should_not_receive(:operation!)
99 |
100 | get "/operation/140x105/image_id/any_name.jpg"
101 |
102 | last_response.status.should == 400
103 | last_response.body.should be_empty
104 | end
105 |
106 | it "should halt 400 on URL with invalid operation/param pair" do
107 | get "/140x105/image_id/any_name.jpg"
108 |
109 | last_response.status.should == 400
110 | last_response.body.should be_empty
111 | end
112 | end
113 |
114 | describe "GET /" do
115 | it "should return ok as healthcheck" do
116 | get "/"
117 |
118 | last_response.should be_ok
119 | last_response.body.should == "ok"
120 | end
121 | end
122 |
123 | describe "cache" do
124 | it "should cache response with max age of 1 day" do
125 | @image.stub!(:to_blob).and_return("image data")
126 | get "/crop/140x105/image_id/any_name.jpg"
127 | last_response.headers["Cache-Control"].should == "public, max-age=31557600"
128 | end
129 | end
130 |
131 | describe "configuration" do
132 | describe "cache duration" do
133 | it "should use the configured cache duration" do
134 | def app
135 | Mugshot::Application.new(:storage => @storage, :cache_duration => 3.days.to_i)
136 | end
137 |
138 | @image.stub!(:to_blob).and_return("image data")
139 | get "/crop/140x105/image_id/any_name.jpg"
140 | last_response.headers["Cache-Control"].should == "public, max-age=#{3.days.to_i}"
141 | end
142 | end
143 |
144 | describe "valid operations" do
145 | it "should allow a valid operation" do
146 | def app
147 | Mugshot::Application.new(:storage => @storage, :valid_operations => ["crop", "resize"])
148 | end
149 |
150 | @image.stub!(:to_blob).and_return("image data")
151 |
152 | get "/resize/200x100/image_id/any_name.jpg"
153 | last_response.should be_ok
154 | last_response.body.should == "image data"
155 | end
156 |
157 | it "should halt with a 400 (Bad Request) when an invalid operation is given" do
158 | def app
159 | Mugshot::Application.new(:storage => @storage, :valid_operations => ["crop", "resize"])
160 | end
161 |
162 | get "/quality/50/image_id/any_name.jpg"
163 | last_response.status.should == 400
164 | end
165 | end
166 |
167 | describe "quality range" do
168 | it "should allow quality operations with values in the configured range" do
169 | def app
170 | Mugshot::Application.new(:storage => @storage, :quality_range => 1..200)
171 | end
172 |
173 | @image.stub!(:to_blob).and_return("image data")
174 |
175 | 1.upto(200) do |quality|
176 | get "/quality/#{quality}/image_id/any_name.jpg"
177 | last_response.should be_ok
178 | last_response.body.should == "image data"
179 | end
180 | end
181 |
182 | it "should allow quality operations when no range is configured" do
183 | @image.stub!(:to_blob).and_return("image data")
184 |
185 | 1.upto(300) do |quality|
186 | get "/quality/#{quality}/image_id/any_name.jpg"
187 | last_response.should be_ok
188 | last_response.body.should == "image data"
189 | end
190 | end
191 |
192 | it "should halt with a 400 (Bad Request) quality operations with values outside the configured range" do
193 | def app
194 | Mugshot::Application.new(:storage => @storage, :quality_range => 1..200)
195 | end
196 |
197 | get "/quality/0/image_id/any_name.jpg"
198 | last_response.status.should == 400
199 |
200 | get "/quality/201/image_id/any_name.jpg"
201 | last_response.status.should == 400
202 | end
203 | end
204 |
205 | describe "allowed sizes" do
206 | def allowed_sizes
207 | ['640x480', '640x360', '480x360', '320x240']
208 | end
209 |
210 | %w{resize crop}.each do |operation|
211 | it "should allow #{operation} operations for configured values" do
212 | def app
213 | Mugshot::Application.new(:storage => @storage, :allowed_sizes => allowed_sizes)
214 | end
215 |
216 | @image.stub!(:to_blob).and_return("image data")
217 |
218 | allowed_sizes.each do |size|
219 | get "/#{operation}/#{size}/image_id/any_name.jpg"
220 | last_response.should be_ok
221 | last_response.body.should == "image data"
222 | end
223 | end
224 |
225 | it "should allow #{operation} operations when allowed sizes is not configured" do
226 | def app
227 | Mugshot::Application.new(:storage => @storage)
228 | end
229 |
230 | @image.stub!(:to_blob).and_return("image data")
231 |
232 | ['300x200', '400x250'].each do |size|
233 | get "/#{operation}/#{size}/image_id/any_name.jpg"
234 | last_response.should be_ok
235 | last_response.body.should == "image data"
236 | end
237 | end
238 |
239 | it "should halt with a 400 (Bad Request) #{operation} operations with a not allowed size" do
240 | def app
241 | Mugshot::Application.new(:storage => @storage, :allowed_sizes => allowed_sizes)
242 | end
243 |
244 | get "/#{operation}/300x200/image_id/any_name.jpg"
245 | last_response.status.should == 400
246 |
247 | get "/#{operation}/480x300/image_id/any_name.jpg"
248 | last_response.status.should == 400
249 | end
250 | end
251 | end
252 |
253 | describe "allowed formats" do
254 | it "should allow valid formats" do
255 | def app
256 | Mugshot::Application.new(:storage => @storage, :allowed_formats => ["jpg", "png"])
257 | end
258 |
259 | @image.stub!(:to_blob).and_return("image data")
260 |
261 | ["jpg", "png"].each do |format|
262 | get "/image_id/any_name.#{format}"
263 | last_response.should be_ok
264 | last_response.body.should == "image data"
265 | end
266 | end
267 |
268 | it "should allow any format when no allowed format is configured" do
269 | @image.stub!(:to_blob).and_return("image data")
270 |
271 | get "/image_id/any_name.tiff"
272 | last_response.should be_ok
273 | last_response.body.should == "image data"
274 | end
275 |
276 | it "should halt with a 400 (Bad Request) when an invalid format is given" do
277 | def app
278 | Mugshot::Application.new(:storage => @storage, :allowed_formats => ["jpg", "png"])
279 | end
280 |
281 | get "image_id/any_name.tiff"
282 | last_response.status.should == 400
283 | end
284 | end
285 |
286 | describe "allowed names" do
287 | it "should allow valid names" do
288 | def app
289 | Mugshot::Application.new(:storage => @storage, :allowed_names => ["some_name", "some_other_name"])
290 | end
291 |
292 | @image.stub!(:to_blob).and_return("image data")
293 |
294 | ["some_name", "some_other_name"].each do |name|
295 | get "/image_id/#{name}.jpg"
296 | last_response.should be_ok
297 | last_response.body.should == "image data"
298 | end
299 | end
300 |
301 | it "should allow any name when no allowed name is configured" do
302 | @image.stub!(:to_blob).and_return("image data")
303 |
304 | get "/image_id/any_name.tiff"
305 | last_response.should be_ok
306 | last_response.body.should == "image data"
307 | end
308 |
309 | it "should halt with a 400 (Bad Request) when an invalid name is given" do
310 | def app
311 | Mugshot::Application.new(:storage => @storage, :allowed_names => ["some_name", "some_other_name"])
312 | end
313 |
314 | get "image_id/any_name.tiff"
315 | last_response.status.should == 400
316 | end
317 | end
318 | end
319 | end
320 |
--------------------------------------------------------------------------------
/spec/mugshot/fs_storage_spec.rb:
--------------------------------------------------------------------------------
1 | # -*- encoding: utf-8 -*-
2 | require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
3 |
4 | describe Mugshot::FSStorage do
5 | before :each do
6 | @fs = Mugshot::FSStorage.new("/tmp/mugshot/spec")
7 | end
8 |
9 | it "should write an image to the filesystem and read it back" do
10 | bin = File.open("spec/files/test.jpg").read
11 |
12 | image = Mugshot::Image.new File.open("spec/files/test.jpg")
13 | Mugshot::Image.stub!(:new).and_return(image)
14 |
15 | id = @fs.write(bin)
16 |
17 | image2 = @fs.read(id)
18 | image2.should == image
19 | end
20 |
21 | it "should return nil when an image with the given id doesn't exist on the filesystem" do
22 | @fs.read('nonexistant-id').should be_nil
23 | end
24 | end
25 |
--------------------------------------------------------------------------------
/spec/mugshot/http_storage_spec.rb:
--------------------------------------------------------------------------------
1 | # -*- encoding: utf-8 -*-
2 |
3 | require File.expand_path(File.dirname(__FILE__) + "/../spec_helper")
4 | require 'fakeweb'
5 |
6 | describe Mugshot::HTTPStorage do
7 | before :each do
8 | @storage = Mugshot::HTTPStorage.new{|id| "http://foo.com:123/any/#{id}/abc.jpg"}
9 | end
10 |
11 | describe 'reading' do
12 | before :each do
13 | FakeWeb.register_uri(:get, 'http://foo.com:123/any/image_id/abc.jpg', :body => "blob")
14 | FakeWeb.register_uri(:get, 'http://foo.com:123/any/does_not_exist/abc.jpg', :status => 404)
15 | end
16 |
17 | it "should return image" do
18 | Mugshot::Image.should_receive(:new).with("blob").and_return("image")
19 |
20 | @storage.read("image_id").should == "image"
21 | end
22 |
23 | it "should return nil if image does not exist in the other mugshot" do
24 | @storage.read("does_not_exist").should be_nil
25 | end
26 | end
27 |
28 | describe 'writing' do
29 | it "should return nil" do
30 | @storage.write('').should be_nil
31 | end
32 | end
33 | end
--------------------------------------------------------------------------------
/spec/mugshot/image_spec.rb:
--------------------------------------------------------------------------------
1 | # -*- encoding: utf-8 -*-
2 | require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
3 |
4 | describe Mugshot::Image do
5 | before :each do
6 | @magick_image = mock(Magick::Image, :columns => 100, :rows => 100).as_null_object
7 | @magick_image.instance_eval do # TODO: Explain it
8 | def to_blob(&block)
9 | block.call if block.present?
10 | return 'blob_data'
11 | end
12 | end
13 |
14 | Magick::Image.stub!(:read).and_return([@magick_image])
15 |
16 | @image = Mugshot::Image.new(File.open("spec/files/test.jpg"))
17 | end
18 |
19 | it 'can be initialized with blob' do
20 | Magick::Image.should_receive(:from_blob).and_return([@magick_image])
21 | Mugshot::Image.new("blob")
22 | end
23 |
24 | describe "to blob" do
25 | it 'should return image as a blob using default options' do
26 | @image.to_blob.should == 'blob_data'
27 | end
28 |
29 | it 'should return image as a blob using format' do
30 | @image.should_receive(:format=).with('png')
31 | @image.to_blob(:format => :png).should == 'blob_data'
32 | end
33 | end
34 |
35 | it 'should return image as a blob using quality' do
36 | @image.should_receive(:quality=).with(75)
37 |
38 | @image.quality!("75")
39 | @image.to_blob
40 | end
41 |
42 | it 'should set background for transparent images' do
43 | canvas = mock(Magick::Image)
44 | Mugshot::MagickFactory.stub!(:create_canvas).and_return(canvas)
45 | canvas.should_receive(:composite).
46 | with(@magick_image, Magick::NorthWestGravity, Magick::OverCompositeOp).
47 | and_return(@magick_image)
48 |
49 | @image.background!("gray")
50 | @image.to_blob
51 | end
52 |
53 | it "should resize image to given width and height" do
54 | @magick_image.should_receive(:resize!).with(300, 200)
55 | @image.resize! "300x200"
56 | end
57 |
58 | it 'should resize image to fit given width' do
59 | @magick_image.should_receive(:resize_to_fit!).with(300, nil)
60 | @image.resize! "300x"
61 | end
62 |
63 | it 'should resize image to fit given height' do
64 | @magick_image.should_receive(:resize_to_fit!).with(nil, 200)
65 | @image.resize! "x200"
66 | end
67 |
68 | it "should crop image to given width and height" do
69 | @magick_image.should_receive(:resize_to_fill!).with(140, 105)
70 | @image.crop! "140x105"
71 | end
72 |
73 | it 'should destroy image' do
74 | @magick_image.should_receive(:'destroy!')
75 | @image.destroy!
76 | end
77 |
78 | end
79 |
80 |
--------------------------------------------------------------------------------
/spec/mugshot/magick_factory_spec.rb:
--------------------------------------------------------------------------------
1 | # -*- encoding: utf-8 -*-
2 | require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
3 |
4 | describe Mugshot::MagickFactory do
5 |
6 | it "should create a canvas given columns, rows and background color" do
7 | canvas = Mugshot::MagickFactory.create_canvas(100, 200, 'red')
8 | canvas.columns.should == 100
9 | canvas.rows.should == 200
10 | canvas.background_color == 'red'
11 | end
12 |
13 | end
14 |
--------------------------------------------------------------------------------
/spec/mugshot/s3_storage_spec.rb:
--------------------------------------------------------------------------------
1 | # -*- encoding: utf-8 -*-
2 | require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
3 | require 'fog'
4 |
5 | describe Mugshot::S3Storage do
6 | before :each do
7 | Fog.mock!
8 | @fs = Mugshot::S3Storage.new(:bucket_name => 'bucket_name', :access_key_id => 'access_key_id', :secret_access_key => 'secret_access_key')
9 | @bin = File.open("spec/files/test.jpg").read
10 | end
11 |
12 | it "should write an image to the filesystem and read it back" do
13 | image = Mugshot::Image.new(File.open("spec/files/test.jpg"))
14 | Mugshot::Image.stub!(:new).and_return(image)
15 |
16 | id = @fs.write(@bin)
17 |
18 | image2 = @fs.read(id)
19 | image2.should == image
20 | end
21 |
22 | it "should return nil when an image with the given id doesn't exist on the filesystem" do
23 | @fs.read('nonexistant-id').should be_nil
24 | end
25 | end
26 |
--------------------------------------------------------------------------------
/spec/mugshot/storage_spec.rb:
--------------------------------------------------------------------------------
1 | # -*- encoding: utf-8 -*-
2 | require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
3 |
4 | describe Mugshot::Storage do
5 | it "should convert an id to path with 6 levels of directories" do
6 | @fs = Mugshot::Storage.new
7 | @fs.send(:id_to_path, "a9657a30c7df012c736512313b021ce1").should == "a9/65/7a/30/c7/df/012c736512313b021ce1"
8 | end
9 | end
10 |
--------------------------------------------------------------------------------
/spec/spec_helper.rb:
--------------------------------------------------------------------------------
1 | # -*- encoding: utf-8 -*-
2 | require "rubygems"
3 | $LOAD_PATH.unshift(File.dirname(__FILE__))
4 | $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
5 | require 'mugshot'
6 |
7 | require 'rspec'
8 | require 'rspec/autorun'
9 | require 'rack/test'
10 | require 'pp'
11 |
12 | def in_editor?
13 | ENV.has_key?('TM_MODE') || ENV.has_key?('EMACS') || ENV.has_key?('VIM')
14 | end
15 |
16 | # Requires supporting files with custom matchers and macros, etc,
17 | # in ./support/ and its subdirectories.
18 | Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each {|f| require f}
19 |
20 | Rspec.configure do |config|
21 | require 'rspec/expectations'
22 | config.include Rspec::Matchers
23 | config.include Rack::Test::Methods
24 |
25 | config.mock_framework = :rspec
26 | config.filter_run :focused => true
27 | config.run_all_when_everything_filtered = true
28 | config.color_enabled = !in_editor?
29 | end
30 |
--------------------------------------------------------------------------------
/spec/test.html:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------