scale_down 0.6.1 → 0.7.0

Sign up to get free protection for your applications and to get access to all the features.
data/CHANGES CHANGED
@@ -1,3 +1,7 @@
1
+ == 2012-01-26==
2
+ Version 0.7.0
3
+ Prefefined labels for image sizes
4
+
1
5
  == 2012-01-24 ==
2
6
  Version 0.6.1
3
7
  Minor change in CMYK conversion
data/README.md CHANGED
@@ -1,64 +1,98 @@
1
1
  [![Build Status](https://secure.travis-ci.org/jweir/ScaleDown.png)](http://travis-ci.org/jweir/ScaleDown)
2
2
 
3
+ The image scaling server used by [Fame Driver](http://famedriver.com).
4
+ It supports dynamic sizes, image cropping and color conversion (CMYK to RGB).
5
+ It only supports images on the same machine (this is not a distributed scaler).
6
+
3
7
  ScaleDown
4
8
  ==========
9
+ So you want to scale an image?
10
+ ------------------------------
5
11
 
6
- An on demand image scaling server.
12
+ You have an image on `example.com`. It has a public path of `/images/john/picture.png`.
13
+ You want it scaled to fit in a 400x400 pixel box. ScaleDown is running on the subdomain `images`.
7
14
 
8
- Images are scaled based upon their URL. An HMAC signature is used to prevent unauthorized scaling of images.
15
+ http://server/images/john/scaled/400x400/picture.png?HMAC_SIGNATURE
9
16
 
10
- Supports cropping images and converts CMYK to RGB.
17
+ The scaled file is saved in a public path identical to the request. It will be statically served on the next request.
11
18
 
12
- URL Schema
13
- ==========
19
+ Geometry, Labels and Cropping
20
+ ==============================
14
21
 
15
- Your file on `example.com` has a public path of `/images/john/picture.png`. You want it scaled to fit in a 400x400 pixel box. **ScaleDown** is running on the subdomain `images`.
22
+ There are two methods of requesting a scaled image: label or geometry.
16
23
 
17
- The URL to your image would be:
24
+ Labels
25
+ ------
18
26
 
19
- http://images.example.com/images/john/scaled/400x400/picture.png?HMAC_SIGNATURE
27
+ A label is a predefined box the scaled image will fit within.
28
+ Labels are defined in the `config.ru` file.
20
29
 
21
- The scaled file is saved in a public path identical to the request. Once an image is scaled your webserve can statically server the file.
30
+ ```sh
31
+ # A label called thumbnail has been defined as "100x100"
32
+ # The below url will scale picture.png to fit into a 100 x 100 box
33
+ http://server/images/john/scaled/thumbnail/picture.png
34
+ ^^^^^^
35
+ ```
22
36
 
23
- The schema is
37
+ ```ruby
38
+ # in your config.ru file
39
+ config.labels = {
40
+ "thumbnail" => "100x100"
41
+ }
42
+ ```
24
43
 
25
- http://:host/:path_to_file/scaled/:geometry/:filename?:hmac_signature
44
+ Geometry
45
+ --------
26
46
 
27
- :host the address running ScaleDown
28
- :path_to_file the public path of the original file
29
- `scaled` keyword
30
- :geometry width x height and an optional `-crop`
31
- :filename the filename of the original image to scale
32
- :hmac_signature security measure to validate the request
47
+ Geometries allow the client to request an image of any size.
33
48
 
49
+ A geometry defines a box the image must fit within: `WIDTHxHEIGHT`.
50
+
51
+
52
+ ```sh
53
+ # scale to a 300 pixel width box by 900 pixel high box
54
+ http://server/images/john/scaled/300x900/picture.png?HMAC_SIGNATURE
55
+ ^^^^^^^
56
+ ```
34
57
 
35
- Geometry & Cropping
36
- ====================
58
+ Either, but not both, dimensions may use the keyword `auto`. This will scale the image to fit the defined dimension.
37
59
 
38
- To crop an image include the `-crop` option. The image will be scaled and cropped to fit the geometry.
60
+ ```sh
61
+ # scale to a 500 pixel wide box, of any height
62
+ http://server/images/john/scaled/500xauto/picture.png?HMAC_SIGNATURE
63
+ ^^^^
64
+ ```
39
65
 
40
- http://images.example.com/images/scaled/400x300-crop/logo.png?A3SDACEDF
66
+ When using a geometry an HMAC is required (see below).
67
+
68
+ Crop
69
+ ----
41
70
 
42
- Using `auto` as a dimension will fit the image to the other dimension.
71
+ To crop an image include the `-crop` option. The image will be scaled and cropped to fit the geometry. This is a simple crop positioned on the center and top of the image.
43
72
 
44
- For example, to ensure an image is 300 pixels wide set the height to auto
73
+ Both geometry and labels accept the `-crop` option.
45
74
 
46
- http://images.example.com/images/scaled/300xauto/logo.png/?A3SDACEDF
75
+ ```sh
76
+ http://server/images/john/scaled/thumbnail-crop/picture.png
77
+ http://server/images/john/scaled/100x100-crop/picture.png
78
+ ```
47
79
 
48
- There is a very simple `/info` function for getting image dimensions. It just returns a string with the WIDTHxHEIGHT.
80
+ Info
81
+ ----
82
+ There is a very simple `/info` function for getting image dimensions. It just returns a string with the WIDTHxHEIGHT of the original image.
49
83
 
50
- http://images.example.com/images/logo.png/info
84
+ http://server/images/logo.png/info
51
85
 
52
86
 
53
- HMAC
54
- ====
87
+ HMAC & Geometry
88
+ ==============
55
89
 
56
- You don't want someone taking down your server with malicious URLs, so an HMAC signature is used. This ensures that your URL was generated by a trusted source.
90
+ An HMAC signature is required to prevent a DOS attack. This ensures that your URL was generated by a trusted source.
57
91
 
58
92
  HMAC requires a shared key between the application generating the URL and the ScaleDown server.
59
93
 
60
- Sample Ruby URL Generator
61
- -------------------------
94
+ Ruby HMAC URL Generator
95
+ ------------------
62
96
 
63
97
  ```ruby
64
98
  require 'ruby-hmac'
@@ -66,12 +100,12 @@ require 'ruby-hmac'
66
100
  def signed_image_url(absolute_path, filename, geometry)
67
101
  shared_secret = "secret"
68
102
  hmac = HMAC::SHA1.new(shared_secret).update([absolute_path, 'scaled', geometry, filename].join("/")).to_s[0...8]
69
- "http://images.example.com#{[absolute_path, 'scaled', geometry, CGI.escape(filename)].join("/")}?#{hmac}"
103
+ "http://server#{[absolute_path, 'scaled', geometry, CGI.escape(filename)].join("/")}?#{hmac}"
70
104
  end
71
105
  ```
72
106
 
73
- Sample Node.js URL Generator
74
- ----------------------------
107
+ Node.js HMAC URL Generator
108
+ ---------------------
75
109
 
76
110
  ```javascript
77
111
  // Uses the Node.js crypot library
@@ -88,8 +122,23 @@ function signed_image_url(absolute_path, filename, geometry){
88
122
  }
89
123
  ```
90
124
 
91
- PNG, JPG, TIFF and other images supported
92
- ========================================
125
+ URL Schema
126
+ ==========
127
+
128
+ The schema is
129
+
130
+ http://:host/:path_to_file/scaled/:target/:filename?:hmac_signature
131
+
132
+ :host the address running ScaleDown
133
+ :path_to_file the public path of the original file
134
+ `scaled` keyword
135
+ :target a label or width x height. An optional `-crop` can be included with either
136
+ :filename the filename of the original image to scale
137
+ :hmac_signature security measure to validate the request(not required for labels)
138
+
139
+
140
+ Supported Image Formats
141
+ =======================
93
142
 
94
143
  ScaleDown will handle any image that Image Magick can process. But there are some rules:
95
144
 
@@ -101,7 +150,9 @@ Installation & Configuration
101
150
 
102
151
  gem install scale_down
103
152
 
104
- Create a Rackup file (config.ru). See https://github.com/jweir/ScaleDown/tree/master/config_sample.ru more options
153
+ Create a Rackup file (config.ru).
154
+
155
+ See https://github.com/jweir/ScaleDown/tree/master/config.sample.ru for all options
105
156
 
106
157
  ```ruby
107
158
  require 'rubygems'
data/config.sample.ru ADDED
@@ -0,0 +1,38 @@
1
+ require 'scale_down'
2
+
3
+ ScaleDown.tap do |config|
4
+
5
+ # The location of the public directory where the original are located
6
+ # This directory will also be the target for writing scaled images
7
+ # Most likely this will be a symbolic link to a shared directory
8
+ config.public_path = "#{File.expand_path(File.dirname(__FILE__))}/public"
9
+
10
+ # Labels are predefined geometries.
11
+ # They have the advantage of not requiring an HMAC signature in the URL
12
+ # The keys should be strings, not symbols
13
+ config.labels = {
14
+ "thumbnail" => "100x100"
15
+ }
16
+
17
+ # an array of the max width and height an image can be scaled, in pixels.
18
+ config.max_dimensions = [1200,1200]
19
+
20
+ # the max file size allowed for the original file to be scaled, in bytes
21
+ scaledown.max_file_size = 25 * 1_048_576 # 25 Megabytes
22
+
23
+ # This is the shared secret for generating and verifying the HMAC signature.
24
+ # This string would be shared with any application generating URLS for scaled images
25
+ # http://www.random.org/strings/
26
+ config.hmac_key = "secret"
27
+
28
+ # Change the method for generating the HMAC
29
+ config.hmac_method = HMAC::SHA1
30
+
31
+ # How long of an HMAC signature is required
32
+ config.hmac_length = 8
33
+
34
+ # Optional logger
35
+ # config.logger = YOUR_LOGGER
36
+ end
37
+
38
+ run ScaleDown::Controller
@@ -31,6 +31,10 @@ module ScaleDown
31
31
  # +Must be set+
32
32
  attr_accessor :public_folder
33
33
 
34
+ # Labels are predefined gemeotries
35
+ attr_accessor :labels
36
+ ScaleDown.labels = Hash.new
37
+
34
38
  def public_folder=(str)
35
39
  @public_folder = str
36
40
  ScaleDown::Controller.public_folder = str
@@ -25,7 +25,7 @@ class ScaleDown::Controller < Sinatra::Application
25
25
  params = {
26
26
  :hmac => request.env["QUERY_STRING"],
27
27
  :filename => parts.pop,
28
- :geometry => parts.pop,
28
+ :target => parts.pop, # the label or geometry
29
29
  :splat => parts
30
30
  }
31
31
 
@@ -54,7 +54,7 @@ class ScaleDown::Controller < Sinatra::Application
54
54
  ScaleDown::Dispatcher.process \
55
55
  :path => params[:splat].join("/"),
56
56
  :filename => params[:filename],
57
- :geometry => params[:geometry],
57
+ :target => params[:target],
58
58
  :hmac => params[:hmac]
59
59
  end
60
60
  end
@@ -37,11 +37,13 @@ class ScaleDown::Dispatcher
37
37
  end
38
38
 
39
39
  def image_options
40
- dimensions, *options = @params[:geometry].split("-")
41
- width, height = dimensions.split("x")
42
- {:height => height.to_i, :width => width.to_i}.tap do |o|
43
- options.each {|k| o[k.to_sym] = true}
44
- end
40
+ width, height = (target_is_label? ? ScaleDown.labels[target] : target).split("x")
41
+
42
+ {
43
+ :height => height.to_i,
44
+ :width => width.to_i,
45
+ :crop => crop?
46
+ }
45
47
  end
46
48
 
47
49
  def scale
@@ -51,8 +53,24 @@ class ScaleDown::Dispatcher
51
53
  :options => image_options
52
54
  end
53
55
 
56
+ def target
57
+ @dimensions ||= @params[:target].split(/-crop\Z/).first
58
+ end
59
+
60
+ def target_is_label?
61
+ @target_is_label ||= ScaleDown.labels.keys.include?(target)
62
+ end
63
+
64
+ def crop?
65
+ @crop ||= ! @params[:target].match(/-crop\Z/).nil?
66
+ end
67
+
54
68
  def valid_hmac?
55
- ScaleDown.valid_hmac?(@params)
69
+ if target_is_label?
70
+ true
71
+ else
72
+ ScaleDown.valid_hmac?(@params)
73
+ end
56
74
  end
57
75
 
58
76
  def redirect_code
@@ -60,7 +78,7 @@ class ScaleDown::Dispatcher
60
78
  end
61
79
 
62
80
  def redirect_path
63
- ["/"+@params[:path], @params[:geometry], scaled_filename].join("/")
81
+ ["/"+@params[:path], @params[:target], scaled_filename].join("/")
64
82
  end
65
83
 
66
84
  def root_file_exists?
@@ -1,3 +1,3 @@
1
1
  module ScaleDown
2
- VERSION = "0.6.1"
2
+ VERSION = "0.7.0"
3
3
  end
data/lib/scale_down.rb CHANGED
@@ -19,7 +19,7 @@ module ScaleDown
19
19
 
20
20
 
21
21
  def self.valid_hmac?(params)
22
- str = ["/", params[:path], "/", params[:geometry], "/", params[:filename]].join
22
+ str = ["/", params[:path], "/", params[:target], "/", params[:filename]].join
23
23
  hmac(str) == params[:hmac]
24
24
  end
25
25
 
@@ -9,5 +9,25 @@ class ScaleDown::Test < Test::Unit::TestCase
9
9
  assert_equal "/tmp/directory", ScaleDown::Controller.public_folder
10
10
  end
11
11
  end
12
+
13
+ context "labels" do
14
+ context "by default" do
15
+ should "be an empty hash" do
16
+ end
17
+ end
18
+
19
+ context "when defined" do
20
+ setup do
21
+ ScaleDown.labels = {
22
+ "thumbnail" => "40x40",
23
+ "large" => "600x600"
24
+ }
25
+ end
26
+
27
+ should "return the defined hash" do
28
+ assert_equal "40x40", ScaleDown.labels["thumbnail"]
29
+ end
30
+ end
31
+ end
12
32
  end
13
33
  end
@@ -12,16 +12,16 @@ class ScaleDown::Controller::Test < Test::Unit::TestCase
12
12
  ScaleDown::Dispatcher.expects(:process).with(
13
13
  :path => "user/path/scaled",
14
14
  :filename => "filename.png",
15
- :geometry => "400x300-crop-grayscale",
15
+ :target => "400x300-crop-grayscale",
16
16
  :hmac => "HMAC").
17
- returns ["path","status"]
17
+ returns ["path","status"]
18
18
 
19
- get '/user/path/scaled/400x300-crop-grayscale/filename.png?HMAC'
19
+ get '/user/path/scaled/400x300-crop-grayscale/filename.png?HMAC'
20
20
  end
21
21
  end
22
22
 
23
23
  context "a valid request" do
24
- should "send the file" do
24
+ should "respond with the file" do
25
25
  ScaleDown::Dispatcher.expects(:process).returns ["/image-path", 302]
26
26
  get "/path/geo/filename?hmac"
27
27
 
@@ -14,7 +14,7 @@ class ScaleDown::Dispatcher::Test < Test::Unit::TestCase
14
14
  @params = {
15
15
  :path => "file/path/scaled",
16
16
  :filename => "filename.png",
17
- :geometry => "400x300-crop",
17
+ :target => "400x300-crop",
18
18
  :hmac => hmac[0...8]
19
19
  }
20
20
  end
@@ -87,8 +87,35 @@ class ScaleDown::Dispatcher::Test < Test::Unit::TestCase
87
87
  end
88
88
  end
89
89
 
90
- context "process response" do
90
+ context "target" do
91
+ context "from a label" do
92
+ setup do
93
+ ScaleDown.labels = { "thumbnail" => "40x50" }
94
+ @subject = ScaleDown::Dispatcher.new :target => "thumbnail"
95
+ end
96
+
97
+ should "use the label's width and height" do
98
+ dim = @subject.image_options
99
+ assert_equal 40, dim[:width]
100
+ assert_equal 50, dim[:height]
101
+ assert_equal false, dim[:crop]
102
+ end
103
+
104
+ should "always valid the hmac" do
105
+ assert @subject.valid_hmac?
106
+ end
107
+
108
+ should "work with the cropped flag" do
109
+ @subject = ScaleDown::Dispatcher.new :target => "thumbnail-crop"
110
+ dim = @subject.image_options
111
+ assert_equal 40, dim[:width]
112
+ assert_equal 50, dim[:height]
113
+ assert_equal true, dim[:crop]
114
+ end
115
+ end
116
+ end
91
117
 
118
+ context "process response" do
92
119
  context "for an existing, unscaled image" do
93
120
  setup do
94
121
  File.expects(:exists?).with("/tmp/file/path/filename.png").returns true
@@ -26,7 +26,7 @@ class ScaleDown::Test < Test::Unit::TestCase
26
26
  @params = {
27
27
  :path => "file/path",
28
28
  :filename => "filename.png",
29
- :geometry => "400x300-crop",
29
+ :target => "400x300-crop",
30
30
  :hmac => hmac[0...8]
31
31
  }
32
32
  end
@@ -42,6 +42,7 @@ class ScaleDown::Test < Test::Unit::TestCase
42
42
 
43
43
  context "integration test" do
44
44
  setup do
45
+
45
46
  FileUtils.mkdir_p("/tmp/scale_down/test_images/example_1")
46
47
  FileUtils.cp fixture_path("files/graphic.png"), "/tmp/scale_down/test_images/example_1/graphic.png"
47
48
  FileUtils.mkdir_p("/tmp/scale_down/test_images/example_2")
@@ -58,7 +59,7 @@ class ScaleDown::Test < Test::Unit::TestCase
58
59
  assert_equal "300x500", last_response.body
59
60
  end
60
61
 
61
- should "get an image and scale it" do
62
+ should "get an image with a geometry and scale it" do
62
63
  valid_get '/test_images/example_1/scaled/400x300-cropped/graphic.png'
63
64
  assert_equal 200, last_response.status
64
65
  assert File.exists?("/tmp/scale_down/test_images/example_1/scaled/400x300-cropped/graphic.png")
@@ -75,6 +76,29 @@ class ScaleDown::Test < Test::Unit::TestCase
75
76
  assert_equal 500, last_response.status
76
77
  assert !File.exists?("/tmp/scale_down/test_images/example_2/scaled/400x300-cropped/invalid_jpeg.jpg")
77
78
  end
79
+
80
+ context "using a label" do
81
+ setup do
82
+ ScaleDown.labels = { "very-large" => "600x600" }
83
+ end
84
+
85
+ should "get an image with a label and scale it" do
86
+ get '/test_images/example_1/scaled/very-large/graphic.png'
87
+ assert_equal 200, last_response.status
88
+ assert File.exists?("/tmp/scale_down/test_images/example_1/scaled/very-large/graphic.png")
89
+
90
+ get '/test_images/example_1/scaled/very-large-crop/graphic.png'
91
+ assert_equal 200, last_response.status
92
+ assert File.exists?("/tmp/scale_down/test_images/example_1/scaled/very-large-crop/graphic.png")
93
+ end
94
+
95
+ context "that does not exist" do
96
+ should "return an error" do
97
+ get '/test_images/example_1/scaled/toosmall/graphic.png'
98
+ assert_equal 403, last_response.status
99
+ end
100
+ end
101
+ end
78
102
  end
79
103
  end
80
104
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: scale_down
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.6.1
4
+ version: 0.7.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,11 +9,11 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-01-24 00:00:00.000000000Z
12
+ date: 2012-01-26 00:00:00.000000000Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: rmagick
16
- requirement: &2153706660 !ruby/object:Gem::Requirement
16
+ requirement: &2152325300 !ruby/object:Gem::Requirement
17
17
  none: false
18
18
  requirements:
19
19
  - - ! '>='
@@ -21,10 +21,10 @@ dependencies:
21
21
  version: '2.1'
22
22
  type: :runtime
23
23
  prerelease: false
24
- version_requirements: *2153706660
24
+ version_requirements: *2152325300
25
25
  - !ruby/object:Gem::Dependency
26
26
  name: sinatra
27
- requirement: &2153706160 !ruby/object:Gem::Requirement
27
+ requirement: &2152324800 !ruby/object:Gem::Requirement
28
28
  none: false
29
29
  requirements:
30
30
  - - ! '>='
@@ -32,10 +32,10 @@ dependencies:
32
32
  version: '1.0'
33
33
  type: :runtime
34
34
  prerelease: false
35
- version_requirements: *2153706160
35
+ version_requirements: *2152324800
36
36
  - !ruby/object:Gem::Dependency
37
37
  name: ruby-hmac
38
- requirement: &2153705700 !ruby/object:Gem::Requirement
38
+ requirement: &2152324340 !ruby/object:Gem::Requirement
39
39
  none: false
40
40
  requirements:
41
41
  - - ! '>='
@@ -43,10 +43,10 @@ dependencies:
43
43
  version: 0.4.0
44
44
  type: :runtime
45
45
  prerelease: false
46
- version_requirements: *2153705700
46
+ version_requirements: *2152324340
47
47
  - !ruby/object:Gem::Dependency
48
48
  name: contest
49
- requirement: &2153705240 !ruby/object:Gem::Requirement
49
+ requirement: &2152323880 !ruby/object:Gem::Requirement
50
50
  none: false
51
51
  requirements:
52
52
  - - ! '>='
@@ -54,10 +54,10 @@ dependencies:
54
54
  version: 0.1.2
55
55
  type: :development
56
56
  prerelease: false
57
- version_requirements: *2153705240
57
+ version_requirements: *2152323880
58
58
  - !ruby/object:Gem::Dependency
59
59
  name: rake
60
- requirement: &2153704780 !ruby/object:Gem::Requirement
60
+ requirement: &2152323420 !ruby/object:Gem::Requirement
61
61
  none: false
62
62
  requirements:
63
63
  - - ! '>='
@@ -65,10 +65,10 @@ dependencies:
65
65
  version: 0.9.2.2
66
66
  type: :development
67
67
  prerelease: false
68
- version_requirements: *2153704780
68
+ version_requirements: *2152323420
69
69
  - !ruby/object:Gem::Dependency
70
70
  name: mocha
71
- requirement: &2153734500 !ruby/object:Gem::Requirement
71
+ requirement: &2152353140 !ruby/object:Gem::Requirement
72
72
  none: false
73
73
  requirements:
74
74
  - - =
@@ -76,10 +76,10 @@ dependencies:
76
76
  version: 0.9.8
77
77
  type: :development
78
78
  prerelease: false
79
- version_requirements: *2153734500
79
+ version_requirements: *2152353140
80
80
  - !ruby/object:Gem::Dependency
81
81
  name: rack-test
82
- requirement: &2153734040 !ruby/object:Gem::Requirement
82
+ requirement: &2152352680 !ruby/object:Gem::Requirement
83
83
  none: false
84
84
  requirements:
85
85
  - - =
@@ -87,7 +87,7 @@ dependencies:
87
87
  version: 0.5.6
88
88
  type: :development
89
89
  prerelease: false
90
- version_requirements: *2153734040
90
+ version_requirements: *2152352680
91
91
  description: ''
92
92
  email:
93
93
  - john@famedriver.com
@@ -102,7 +102,7 @@ files:
102
102
  - README.md
103
103
  - Rakefile
104
104
  - color_profiles/sRGB.icm
105
- - config_sample.ru
105
+ - config.sample.ru
106
106
  - lib/scale_down.rb
107
107
  - lib/scale_down/configuration.rb
108
108
  - lib/scale_down/controller.rb
data/config_sample.ru DELETED
@@ -1,21 +0,0 @@
1
- require 'scale_down'
2
-
3
- ScaleDown.tap do |config|
4
-
5
- # This is the shared secret. Use something strong
6
- # Perhaps a visit to http://www.random.org/strings/?num=10&len=20&digits=on&upperalpha=on&format=html&rnd=new
7
- config.hmac_key = "secret"
8
-
9
- # You can use a different HMAC, see the ruby-hmac gen
10
- config.hmac_method = HMAC::SHA1
11
-
12
- # The length of the HMAC signature to use
13
- config.hmac_length = 8
14
-
15
- # The location of the public directory for serving static files
16
- # This might be redudant since it will always, maybe, maybe not, be the same as root_path
17
- config.public_path = "#{File.expand_path(File.dirname(__FILE__))}/public"
18
-
19
- end
20
-
21
- run ScaleDown::Controller