photo-cook 1.0.2 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 6e15cd4a81a3e2b2873faca2e0c1bc33788b230d
4
- data.tar.gz: 96578e33f51efdf6b2300f3db3d5d815713883c9
3
+ metadata.gz: 30713968f97de16d9fa2a2d0c3b88bcb5435ac43
4
+ data.tar.gz: 6deed34ded832a9041bb6adf6bd5595acb2ae170
5
5
  SHA512:
6
- metadata.gz: 6b14c7e899302a98bb69d667be131b960587f323bb6c60bd4dfbb1ba40c90287c5883465b2f751e32bf383d46373678a7c86c38803e4bf80d2ad752385296075
7
- data.tar.gz: c26e25973b242036bca129b90b425321f223f714aa11688ec873940844b5aa703ba4adbfc0d446c33625f2d4876bbfbbaec13e8f3f0c0455f3b8cdf9e82683f2
6
+ metadata.gz: 5c76ef21599b1cb9d4c5e870b77afc2748aa7ba65613b7b46246a6850c9e3aee716182c45c4fdc100951746af0feb2a9ed910df90fd12cc68d3ad47e33cfd081
7
+ data.tar.gz: a87c0fdaf2d015ac0b9aafd5b551d64c18c864f21f23d514c6ddd9989759df01cc108cbeb9a6f447435f5fdb001889479cf329c29d07e46c84502716abd89ed2
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- photo-cook (1.0.1)
4
+ photo-cook (1.0.2)
5
5
  mini_magick (~> 4.0)
6
6
  rack (~> 1.5)
7
7
 
@@ -0,0 +1,114 @@
1
+ (function(root) {
2
+ var PhotoCook = {
3
+ resizeDir: <%= PhotoCook.cache_dir.to_json %>,
4
+
5
+ commandRegex: /\-(?:(?:\d+x\d+)|(?:\d+x)|(?:x\d+))(?:crop)?$/,
6
+
7
+ initialize: function() {
8
+ PhotoCook.persistPixelRatio();
9
+ },
10
+
11
+ pixelRatio: (function () {
12
+ // https://gist.github.com/marcedwards/3446599
13
+ var mediaQuery = [
14
+ '(-webkit-min-device-pixel-ratio: 1.3)',
15
+ '(-o-min-device-pixel-ratio: 13/10)',
16
+ 'min-resolution: 120dpi'
17
+ ].join(', ');
18
+
19
+ var ratio = window.devicePixelRatio;
20
+
21
+ // If no ratio found check if screen is retina
22
+ // and if so return 2x ratio
23
+ if (ratio == null && typeof window.matchMedia === 'function') {
24
+ if (window.matchMedia(mediaQuery).matches) {
25
+ ratio = 2;
26
+ }
27
+ }
28
+
29
+ return parseFloat(ratio == null ? 1 : ratio);
30
+ })(),
31
+
32
+ persistPixelRatio: function() {
33
+ var date = new Date();
34
+
35
+ // Expires in 1 year
36
+ date.setTime(date.getTime() + 365 * 24 * 60 * 60 * 1000);
37
+ var expires = 'expires=' + date.toUTCString();
38
+ document.cookie = "PhotoCookPixelRatio=" + PhotoCook.pixelRatio + '; ' + expires;
39
+ },
40
+
41
+ resize: function (path, width, height, options) {
42
+ var crop = typeof options === 'boolean' ? !!options : !!(options && options.crop);
43
+ var ratio = Math.max(0, options && options.pixelRatio) || PhotoCook.pixelRatio;
44
+
45
+ var command = '-' + (width && width !== 'auto' ? Math.round(width * ratio) : '')
46
+ + 'x'
47
+ + (height && height !== 'auto' ? Math.round(height * ratio) : '')
48
+ + (crop ? 'crop' : '');
49
+
50
+ var index; index = path.lastIndexOf('.');
51
+ if (index < 0) { index = path.lastIndexOf('?'); }
52
+ if (index < 0) { index = path.lastIndexOf('#'); }
53
+
54
+ var leftPart = path.slice(0, Math.max(path.lastIndexOf('/'), 0));
55
+ var rightPart = index >= 0 ? path.slice(index) : '';
56
+
57
+ var basename = path.slice(
58
+ leftPart ? leftPart.length + 1 : 0,
59
+ rightPart ? 0 - rightPart.length : path.length
60
+ );
61
+
62
+ return (leftPart ? leftPart + '/' : leftPart)
63
+ + PhotoCook.resizeDir
64
+ + '/'
65
+ + basename
66
+ + command
67
+ + rightPart;
68
+ },
69
+
70
+ strip: function (path) {
71
+ var token = PhotoCook.resizeDir + '/';
72
+ var leftIndex = path.lastIndexOf(token);
73
+ if (leftIndex < 0 || (leftIndex > 0 && path[leftIndex - 1] !== '/')) {
74
+ return path;
75
+ }
76
+
77
+ var rightIndex; rightIndex = path.lastIndexOf('.');
78
+ if (rightIndex < 0) { rightIndex = path.lastIndexOf('?'); }
79
+ if (rightIndex < 0) { rightIndex = path.lastIndexOf('#'); }
80
+
81
+ var basename = path.slice(
82
+ leftIndex + token.length,
83
+ rightIndex >= 0 ? 0 - path.length + rightIndex : path.length
84
+ );
85
+
86
+ var oldLength = basename.length;
87
+
88
+ basename = basename.replace(PhotoCook.commandRegex, '');
89
+
90
+ if (oldLength === basename.length) {
91
+ return path;
92
+ }
93
+
94
+ var leftPart = path.slice(0, leftIndex);
95
+ var rightPart = rightIndex >= 0 ? path.slice(rightIndex) : '';
96
+
97
+ return leftPart + basename + rightPart;
98
+ },
99
+
100
+ uriRegex: /^[-a-z]+:\/\/|^(?:cid|data):|^\/\//i,
101
+
102
+ // Returns true if given URL can produce request to PhotoCook middleware on server
103
+ isServableURL: function(url) {
104
+ // By default check that URL is relative
105
+ return !PhotoCook.uriRegex.test(url);
106
+ }
107
+ };
108
+
109
+ root.PhotoCook = PhotoCook;
110
+ })(this);
111
+
112
+ PhotoCook.initialize();
113
+
114
+ // navigator.cookieEnabled
@@ -0,0 +1 @@
1
+ //= require ./photo-cook/photo-cook
@@ -0,0 +1,27 @@
1
+ module PhotoCook
2
+ module Assemble
3
+
4
+ # Edit URI so it will point to PhotoCook::Middleware
5
+ # NOTE: This method performs no validation
6
+ def assemble_uri(uri, width, height, pixel_ratio, crop)
7
+ result = File.join cache_dir, dirname_or_blank(uri),
8
+ assemble_command(width, height, pixel_ratio, crop), File.basename(uri)
9
+ uri.start_with?('/') ? '/' + result : result
10
+ end
11
+
12
+ # Edit path so it will point to place where resized photo stored
13
+ # NOTE: This method performs no validation
14
+ def assemble_store_path(path, width, height, pixel_ratio, crop)
15
+ rootless = path.split(File.join(root, public_dir)).second
16
+ File.join root, public_dir,
17
+ cache_dir, dirname_or_blank(rootless),
18
+ assemble_command(width, height, pixel_ratio, crop), File.basename(path)
19
+ end
20
+
21
+ private
22
+ def dirname_or_blank(path)
23
+ (dirname = File.dirname(path)) == '.' ? '' : dirname
24
+ end
25
+ end
26
+ extend Assemble
27
+ end
@@ -0,0 +1,8 @@
1
+ module PhotoCook
2
+ module Callbacks
3
+ def on_resize(photo, command)
4
+ log_resize(photo, command) if rails?
5
+ end
6
+ end
7
+ extend Callbacks
8
+ end
@@ -1,9 +1,15 @@
1
1
  module PhotoCook
2
2
  module CarrierWave
3
-
4
3
  def resize(*args)
5
4
  PhotoCook.resize(url, *args)
6
5
  end
7
6
 
7
+ def hresize(*args)
8
+ PhotoCook.hresize(url, *args)
9
+ end
10
+
11
+ def vresize(*args)
12
+ PhotoCook.vresize(url, *args)
13
+ end
8
14
  end
9
15
  end
@@ -0,0 +1,35 @@
1
+ module PhotoCook
2
+ module Command
3
+ # Proportional support
4
+ # http://stackoverflow.com/questions/7200909/imagemagick-convert-to-fixed-width-proportional-height
5
+ #
6
+ # Device pixel ratio collection
7
+ # http://dpi.lv/
8
+ # http://www.canbike.org/CSSpixels/
9
+ def command_regex
10
+ @command_regex ||= %r{
11
+ width= (?<width> auto|\d{1,4}) &
12
+ height= (?<height> auto|\d{1,4}) &
13
+ pixel_ratio= (?<pixel_ratio>[1234]) &
14
+ crop= (?<crop> yes|no)
15
+ }x
16
+ end
17
+
18
+ # NOTE: This method performs no validation
19
+ def assemble_command(width, height, pixel_ratio, crop)
20
+ "width=#{ width == 0 ? 'auto' : width}&" +
21
+ "height=#{ height == 0 ? 'auto' : height}&" +
22
+ "pixel_ratio=#{pixel_ratio.ceil}&" +
23
+ "crop=#{ bool_to_crop(crop)}&"
24
+ end
25
+
26
+ def crop_to_bool(crop)
27
+ crop == 'yes'
28
+ end
29
+
30
+ def bool_to_crop(crop)
31
+ crop ? 'yes' : 'no'
32
+ end
33
+ end
34
+ extend Command
35
+ end
@@ -0,0 +1,36 @@
1
+ module PhotoCook
2
+ module Dimensions
3
+ def parse_and_check_dimensions(unsafe_width, unsafe_height)
4
+ width = unsafe_width == :auto ? 0 : unsafe_width.to_i
5
+ height = unsafe_height == :auto ? 0 : unsafe_height.to_i
6
+
7
+ check_dimensions!(width, height)
8
+ [width, height]
9
+ end
10
+
11
+ def check_dimensions!(width, height)
12
+ raise WidthOutOfBoundsError if width < 0 || width > 9999
13
+ raise HeightOutOfBoundsError if height < 0 || height > 9999
14
+ raise NoConcreteDimensionsError if width + height == 0
15
+ end
16
+ end
17
+
18
+ class WidthOutOfBoundsError < ArgumentError
19
+ def initialize
20
+ super 'Width must be positive integer number (0...9999)'
21
+ end
22
+ end
23
+
24
+ class HeightOutOfBoundsError < ArgumentError
25
+ def initialize
26
+ super 'Height must be positive integer number (0...9999)'
27
+ end
28
+ end
29
+
30
+ class NoConcreteDimensionsError < ArgumentError
31
+ def initialize
32
+ super "Both width and height specified as 'auto'"
33
+ end
34
+ end
35
+ extend Dimensions
36
+ end
@@ -1,9 +1,11 @@
1
1
  module PhotoCook
2
2
  class Engine < ::Rails::Engine
3
+ config.before_initialize do
4
+ PhotoCook.root = Rails.root
5
+ end
3
6
 
4
- initializer :javascripts do |app|
7
+ initializer :photo_cook_javascripts do |app|
5
8
  app.config.assets.paths << File.join(PhotoCook::Engine.root, 'app/assets/javascripts')
6
9
  end
7
-
8
10
  end
9
11
  end
@@ -0,0 +1,18 @@
1
+ module PhotoCook
2
+ module Logging
3
+ def log_resize(photo, w, h, px_ratio, crop, msec)
4
+ msg = %{
5
+ [PhotoCook] Resized photo.
6
+ Source file: #{photo.source_path}
7
+ Resized file: #{photo.resized_path}
8
+ Width: #{w == 0 ? 'auto': "#{w}px"}
9
+ Height: #{h == 0 ? 'auto': "#{h}px"}
10
+ Crop: #{crop ? 'yes' : 'no'}
11
+ Pixel ratio: #{px_ratio}
12
+ Completed in: #{msec.round(1)}ms
13
+ }
14
+ rails_env? ? Rails.logger.info(msg) : print(msg)
15
+ end
16
+ end
17
+ extend Logging
18
+ end
@@ -1,6 +1,5 @@
1
1
  # To use this middleware you should configure application:
2
- # application.config.middleware.insert_before(Rack::Sendfile, PhotoCook::Middleware, Rails.root)
3
- require 'rack'
2
+ # application.config.middleware.insert_before(Rack::Sendfile, PhotoCook::Middleware, Rails.root)
4
3
 
5
4
  module PhotoCook
6
5
  class Middleware
@@ -9,99 +8,98 @@ module PhotoCook
9
8
  @app, @root = app, root
10
9
  end
11
10
 
12
- # The example will be concentrated around uri:
13
- # /uploads/photos/resized/car-640x320crop.png
11
+ # Consider we have car.png in /uploads/photos/1
12
+ # We want to resize it with the following params:
13
+ # Width: choose automatically
14
+ # Height: exactly 640px
15
+ # Crop: yes
16
+ # Pixel ratio: 1
17
+ #
18
+ # Middleware will handle this URI:
19
+ # /resized/uploads/photos/1/width:auto&height:640&crop:true&pixel_ratio:1/car.png
20
+ #
14
21
  def call(env)
15
22
  uri = extract_uri(env)
16
23
 
17
- return default_actions(env) unless
24
+ return default_actions(env) if
18
25
 
19
- # Lets ensure that directory matches PhotoCook.resize_dir
20
- # dir = /uploads/photos/resized
21
- # File::SEPARATOR + PhotoCook.resize_dir = /resized
22
- # dir.chomp! = /uploads/photos
23
- (dir = File.dirname(uri)).chomp!(File::SEPARATOR + PhotoCook.resize_dir) &&
26
+ # Check if uri starts with '/resized'
27
+ false == uri.start_with?(PhotoCook.resize_uri_indicator) ||
24
28
 
25
- # Lets ensure that photo_basename ends with resize command
26
- # photo_name = car-640x320crop.png
27
- # photo_basename = car-640x320crop
28
- # photo_basename.sub! = car.png
29
- (photo_name = File.basename(uri)) &&
30
- (photo_basename = File.basename(photo_name, '.*')).sub!(command_regex, '')
29
+ # Check if uri has valid resize command.
30
+ # uri.split('/')[-2] => width:auto&height:640&crop:true&pixel_ratio:1
31
+ nil == (uri.split('/')[-2] =~ PhotoCook.command_regex) ||
31
32
 
32
- return default_actions(env) if requested_file_exists?(uri)
33
-
34
- # Regex match: _640x320crop
35
- command = Regexp.last_match
33
+ # If for some reasons file exists but request went to Ruby app
34
+ true == requested_file_exists?(uri)
36
35
 
37
36
  # At this point we are sure that this request is targeting to resize photo
38
37
 
39
- # Lets assemble path of the source photo
40
- source_path = assemble_source_path(dir, photo_basename + File.extname(photo_name))
38
+ # Assemble path of the source photo:
39
+ # => /uploads/photos/1/car.png
40
+ source_path = assemble_source_path(uri)
41
41
 
42
- # Finally resize photo
43
- resizer = PhotoCook::Resizer.instance
42
+ # Matched data: width:auto&height:640&crop:true&pixel_ratio:1
43
+ command = Regexp.last_match
44
44
 
45
- # Resizer will store photo in resize directory
46
- photo = resizer.resize source_path, command[:width].to_i, command[:height].to_i, !!command[:crop]
45
+ # Map crop option values: 'yes' => true, 'no' => false
46
+ crop = PhotoCook.crop_to_bool(command[:crop])
47
47
 
48
- if photo
49
- log(photo, source_path, uri, command) if defined?(Rails)
48
+ # Finally resize photo
49
+ # Resized photo will appear in resize directory
50
+ photo = PhotoCook.resize_photo(source_path,
51
+ command[:width], command[:height], pixel_ratio: command[:pixel_ratio], crop: crop)
50
52
 
51
- # http://rubylogs.com/writing-rails-middleware/
52
- # https://viget.com/extend/refactoring-patterns-the-rails-middleware-response-handler
53
- status, headers, body = Rack::File.new(File.join(@root, PhotoCook.public_dir)).call(env)
54
- response = Rack::Response.new(body, status, headers)
55
- response.finish
53
+ if photo
54
+ respond_with_file(env)
56
55
  else
57
56
  default_actions(env)
58
57
  end
59
58
  end
60
59
 
61
- private
60
+ private
62
61
 
63
- def assemble_source_path(dir, photo_name)
64
- URI.decode File.join(@root, PhotoCook.public_dir, dir, photo_name)
62
+ def assemble_source_path(resize_uri)
63
+ # Take URI:
64
+ # /resized/uploads/photos/1/width:auto&height:640&crop:true&pixel_ratio:1/car.png
65
+ #
66
+ # Split by file separator:
67
+ # ["", "resized", "uploads", "photos", "1", "width:auto&height:640&crop:true&pixel_ratio:1", "car.png"]
68
+ #
69
+ els = resize_uri.split('/')
70
+
71
+ # Delete PhotoCook directory:
72
+ # ["", "uploads", "photos", "1", "width:auto&height:640&crop:true&pixel_ratio:1", "car.png"]
73
+ els.delete_at(1)
74
+
75
+ # Delete command string:
76
+ # ["", "uploads", "photos", "1", "car.png"]
77
+ els.delete_at(-2)
78
+
79
+ URI.decode File.join(@root, PhotoCook.public_dir, els)
65
80
  end
66
81
 
67
82
  def requested_file_exists?(uri)
68
- # /my_awesome_project_root/public/uploads/photos/resized/car-640x320crop.png
83
+ # Check if file exists:
84
+ # /application/public/resized/uploads/photos/1/width:auto&height:640&crop:true&pixel_ratio:1/car.png
69
85
  File.exists? File.join(@root, PhotoCook.public_dir, uri)
70
86
  end
71
87
 
72
88
  def extract_uri(env)
73
- Rack::Utils.unescape(env['PATH_INFO'])
89
+ # Remove query string and fragment
90
+ Rack::Utils.unescape(env['PATH_INFO'].gsub(/[\?#].*\z/, ''))
74
91
  end
75
92
 
76
93
  def default_actions(env)
77
94
  @app.call(env)
78
95
  end
79
96
 
80
- # Proportional support
81
- # http://stackoverflow.com/questions/7200909/imagemagick-convert-to-fixed-width-proportional-height
82
- def command_regex
83
- unless @r_command
84
- w = /(?<width>\d+)/
85
- h = /(?<height>\d+)/
86
- @r_command = %r{
87
- \- (?:(?:#{w}x#{h}) | (?:#{w}x) | (?:x#{h})) (?<crop>crop)? \z
88
- }x
89
- end
90
- @r_command
91
- end
92
-
93
- def log(photo, source_path, resized_path, command)
94
- w = command[:width].to_i
95
- h = command[:height].to_i
96
- crop = !!command[:crop]
97
- Rails.logger.info %{
98
- [PhotoCook] Resized photo.
99
- Source file: "#{source_path}".
100
- Resized file: "#{resized_path}".
101
- Width: #{w == 0 ? 'auto': "#{w}px"}.
102
- Height: #{h == 0 ? 'auto': "#{h}px"}.
103
- Crop: #{crop ? 'yes' : 'no'}.
104
- }
97
+ def respond_with_file(env)
98
+ # http://rubylogs.com/writing-rails-middleware/
99
+ # https://viget.com/extend/refactoring-patterns-the-rails-middleware-response-handler
100
+ status, headers, body = Rack::File.new(File.join(@root, PhotoCook.public_dir)).call(env)
101
+ response = Rack::Response.new(body, status, headers)
102
+ response.finish
105
103
  end
106
104
  end
107
105
  end
@@ -0,0 +1,12 @@
1
+ module PhotoCook
2
+ class << self
3
+ attr_accessor :public_dir, :cache_dir, :root
4
+
5
+ def resize_uri_indicator
6
+ '/' + cache_dir
7
+ end
8
+ end
9
+
10
+ self.public_dir = 'public'
11
+ self.cache_dir = 'resized'
12
+ end
@@ -0,0 +1,19 @@
1
+ module PhotoCook
2
+ module PixelRatioSpy
3
+ extend ActiveSupport::Concern
4
+
5
+ included do
6
+ before_action :pass_pixel_ratio
7
+ end
8
+
9
+ def pass_pixel_ratio
10
+ ratio = cookies[:PhotoCookPixelRatio].to_f
11
+ # Dont set pixel ratio if for some reasons it is invalid
12
+ PhotoCook.client_pixel_ratio = PhotoCook.valid_pixel_ratio?(ratio) ? ratio : nil
13
+ end
14
+ end
15
+
16
+ class << self
17
+ attr_accessor :client_pixel_ratio
18
+ end
19
+ end
@@ -0,0 +1,29 @@
1
+ module PhotoCook
2
+ module PixelRatio
3
+ def parse_and_check_pixel_ratio(unsafe_ratio)
4
+ pixel_ratio = unsafe_ratio.to_f
5
+
6
+ raise PixelRatioInvalidOrInfiniteError if pixel_ratio.nan? || pixel_ratio.infinite?
7
+ raise PixelRatioOutOfBoundsError if pixel_ratio < 1 || pixel_ratio > 4
8
+
9
+ pixel_ratio
10
+ end
11
+
12
+ def valid_pixel_ratio?(ratio)
13
+ !ratio.nan? && !ratio.infinite? && ratio >= 1 && ratio <= 4
14
+ end
15
+ end
16
+
17
+ class PixelRatioInvalidOrInfiniteError < ArgumentError
18
+ def initialize
19
+ super 'Given pixel ratio is invalid number or infinite'
20
+ end
21
+ end
22
+
23
+ class PixelRatioOutOfBoundsError < ArgumentError
24
+ def initialize
25
+ super 'Pixel ratio must be positive number: 1 <= pixel_ratio <= 4'
26
+ end
27
+ end
28
+ extend PixelRatio
29
+ end
@@ -1,5 +1,3 @@
1
- require 'mini_magick'
2
-
3
1
  module PhotoCook
4
2
  class Resizer
5
3
  include Singleton
@@ -7,11 +5,11 @@ module PhotoCook
7
5
  CENTER_GRAVITY = 'Center'.freeze
8
6
  TRANSPARENT_BACKGROUND = 'rgba(255,255,255,0.0)'.freeze
9
7
 
10
- def resize(photo_path, width, height, crop = false)
8
+ def resize(photo_path, width, height, pixel_ratio = 1.0, crop = false)
11
9
  if crop
12
- resize_to_fill photo_path, width, height
10
+ resize_to_fill(photo_path, width, height, pixel_ratio)
13
11
  else
14
- resize_to_fit photo_path, width, height
12
+ resize_to_fit(photo_path, width, height, pixel_ratio)
15
13
  end
16
14
  end
17
15
 
@@ -20,19 +18,15 @@ module PhotoCook
20
18
  # - new dimensions will be not larger then the specified
21
19
  #
22
20
  # https://github.com/carrierwaveuploader/carrierwave/blob/71cb18bba4a2078524d1ea683f267d3a97aa9bc8/lib/carrierwave/processing/mini_magick.rb#L131
23
- def resize_to_fit(photo_path, width, height)
21
+ def resize_to_fit(photo_path, width, height, pixel_ratio)
24
22
 
25
23
  # Do nothing if photo is not valid so exceptions will be not thrown
26
- return unless (photo = open(photo_path)).try(:valid?)
24
+ return unless (photo = open(photo_path)) && photo.valid?
27
25
 
28
- width, height = parse_dimensions(width, height)
29
- store_path = PhotoCook.assemble_path(photo_path, width, height, false)
26
+ store_path = assemble_store_path(photo_path, width, height, pixel_ratio, false)
27
+ width, height = multiply_dimensions(width, height, pixel_ratio)
30
28
 
31
- if width > 0 || height > 0
32
- photo.combine_options do |cmd|
33
- cmd.resize "#{width == 0 ? nil : width}x#{height == 0 ? nil : height}>"
34
- end
35
- end
29
+ photo.combine_options { |cmd| cmd.resize "#{literal_dimensions(width, height)}>" }
36
30
 
37
31
  store(photo, store_path)
38
32
  end
@@ -42,36 +36,41 @@ module PhotoCook
42
36
  # - the photo will be cropped if necessary
43
37
  #
44
38
  # https://github.com/carrierwaveuploader/carrierwave/blob/71cb18bba4a2078524d1ea683f267d3a97aa9bc8/lib/carrierwave/processing/mini_magick.rb#L176
45
- def resize_to_fill(photo_path, width, height)
39
+ def resize_to_fill(photo_path, width, height, pixel_ratio)
46
40
 
47
41
  # Do nothing if photo is not valid so exceptions will be not thrown
48
- return unless (photo = open(photo_path)).try(:valid?)
49
-
50
- width, height = parse_dimensions(width, height)
51
- cols, rows = photo[:dimensions]
52
- store_path = PhotoCook.assemble_path(photo_path, width, height, true)
53
-
54
- if width > 0 || height > 0
55
- photo.combine_options do |cmd|
56
- if width != cols || height != rows
57
- scale_x = width / cols.to_f
58
- scale_y = height / rows.to_f
59
- if scale_x >= scale_y
60
- cols = (scale_x * (cols + 0.5)).round
61
- rows = (scale_x * (rows + 0.5)).round
62
- cmd.resize "#{cols}>"
63
- else
64
- cols = (scale_y * (cols + 0.5)).round
65
- rows = (scale_y * (rows + 0.5)).round
66
- cmd.resize "x#{rows}>"
67
- end
68
- end
69
- cmd.gravity CENTER_GRAVITY
70
- cmd.background TRANSPARENT_BACKGROUND
71
- if cols != width || rows != height
72
- cmd.extent "#{width == 0 ? nil : width}x#{height == 0 ? nil : height}>"
42
+ return unless (photo = open(photo_path)) && photo.valid?
43
+
44
+ store_path = assemble_store_path(photo_path, width, height, pixel_ratio, true)
45
+ cols, rows = photo[:dimensions]
46
+ mwidth, mheight = multiply_dimensions(width, height, pixel_ratio)
47
+
48
+ # TODO
49
+ # Original dimensions are 1000x800. You want 640x640@1x. You will get 640x640
50
+ # Original dimensions are 1000x800. You want 640x640@2x. You will get 800x800
51
+ # Original dimensions are 1000x800. You want 640x640@3x. You will get 800x800
52
+ # Original dimensions are 1000x800. You want 1280x1280@1x. You will get ?
53
+ # Original dimensions are 1000x800. You want 1000x1280@1x. You will get ?
54
+
55
+ photo.combine_options do |cmd|
56
+ if width != cols || height != rows
57
+ scale_x = width / cols.to_f
58
+ scale_y = height / rows.to_f
59
+ if scale_x >= scale_y
60
+ cols = (scale_x * (cols + 0.5)).round
61
+ rows = (scale_x * (rows + 0.5)).round
62
+ cmd.resize "#{cols}>"
63
+ else
64
+ cols = (scale_y * (cols + 0.5)).round
65
+ rows = (scale_y * (rows + 0.5)).round
66
+ cmd.resize "x#{rows}>"
73
67
  end
74
68
  end
69
+ cmd.gravity CENTER_GRAVITY
70
+ cmd.background TRANSPARENT_BACKGROUND
71
+ if cols != width || rows != height
72
+ cmd.extent "#{literal_dimensions(width, height)}>"
73
+ end
75
74
  end
76
75
 
77
76
  store(photo, store_path)
@@ -91,22 +90,24 @@ module PhotoCook
91
90
  end
92
91
 
93
92
  def store(resized_photo, path_to_store_at)
94
- dir = File.dirname path_to_store_at
95
- Dir.mkdir dir unless File.exists?(dir)
96
- resized_photo.write path_to_store_at
93
+ dir = File.dirname(path_to_store_at)
94
+ FileUtils.mkdir_p(dir) unless File.exists?(dir)
95
+
96
+ resized_photo.write(path_to_store_at)
97
97
  resized_photo.resized_path = path_to_store_at
98
98
  resized_photo
99
99
  end
100
100
 
101
- def parse_dimensions(width, height)
102
- width = width == :auto ? 0 : width.to_i
103
- height = height == :auto ? 0 : height.to_i
104
- check_dimensions!(width, height)
105
- [width, height]
101
+ def literal_dimensions(width, height)
102
+ "#{width == 0 ? nil : width}x#{height == 0 ? nil : height}"
103
+ end
104
+
105
+ def assemble_store_path(path, width, height, pixel_ratio, crop)
106
+ PhotoCook.assemble_store_path(path, width, height, pixel_ratio, crop)
106
107
  end
107
108
 
108
- def check_dimensions!(width, height)
109
- raise ArgumentError, 'Expected positive numbers ' if width < 0 || height < 0
109
+ def multiply_dimensions(width, height, ratio)
110
+ [(width * ratio).round, (height * ratio).round]
110
111
  end
111
112
  end
112
113
  end
@@ -0,0 +1,68 @@
1
+ module PhotoCook
2
+ module Resizing
3
+
4
+ # Performs photo resizing:
5
+ # resize_photo('/application/public/uploads/car.png', 280, 280)
6
+ #
7
+ # NOTE: This method will perform validation
8
+ def resize_photo(path, width, height, options = {})
9
+ started = Time.now
10
+
11
+ width, height = parse_and_check_dimensions(width, height)
12
+ pixel_ratio, crop = open_options(options)
13
+ # Explicit # Default
14
+ pixel_ratio = unify_pixel_ratio(parse_and_check_pixel_ratio(pixel_ratio || 1))
15
+ photo = Resizer.instance.resize(path, width, height, pixel_ratio, !!crop)
16
+
17
+ finished = Time.now
18
+ log_resize(photo, width, height, pixel_ratio, !!crop, (finished - started) * 1000.0)
19
+ photo
20
+ end
21
+
22
+ # Builds URI for resizing:
23
+ # resize('/uploads/car.png', 280, 280, pixel_ratio: 2.5)
24
+ # => /resized/uploads/width:280&height:280&crop:0&pixel_ratio:3
25
+ #
26
+ # NOTE: This method will perform validation
27
+ def build_resize_uri(uri, width, height, options = {})
28
+ width, height = parse_and_check_dimensions(width, height)
29
+ pixel_ratio, crop = open_options(options)
30
+ # Explicit # From cookies # Default
31
+ pixel_ratio = parse_and_check_pixel_ratio(pixel_ratio || PhotoCook.client_pixel_ratio || 1)
32
+ assemble_uri(uri, width, height, unify_pixel_ratio(pixel_ratio), !!crop)
33
+ end
34
+
35
+ alias resize build_resize_uri
36
+
37
+ # Shorthand: hresize('/uploads/car.png', 280) <=> resize('/uploads/car.png', 280, nil)
38
+ def hresize(uri, width, options = {})
39
+ resize(uri, width, :auto, options)
40
+ end
41
+
42
+ # Shorthand: vresize('/uploads/car.png', 280) <=> resize('/uploads/car.png', nil, 280)
43
+ def vresize(uri, height, options = {})
44
+ resize(uri, :auto, height, options)
45
+ end
46
+
47
+ private
48
+ def open_options(options)
49
+ case options
50
+ when Hash
51
+ [options[:pixel_ratio], options.fetch(:crop, false)]
52
+ when Symbol
53
+ [nil, options == :crop]
54
+ else
55
+ [nil, nil]
56
+ end
57
+ end
58
+
59
+ # Do not produce various number of pixel ratios:
60
+ # 2.5 => 3
61
+ # 2.1 => 3
62
+ def unify_pixel_ratio(px_ratio)
63
+ px_ratio.ceil
64
+ end
65
+ end
66
+
67
+ extend Resizing
68
+ end
@@ -1,3 +1,3 @@
1
1
  module PhotoCook
2
- VERSION = '1.0.2'
2
+ VERSION = '1.1.0'
3
3
  end
data/lib/photo-cook.rb CHANGED
@@ -1,41 +1,27 @@
1
- module PhotoCook
2
-
3
- mattr_writer :public_dir
4
- mattr_writer :resize_dir
5
-
6
- def self.public_dir
7
- @public_dir || 'public'
8
- end
9
-
10
- def self.resize_dir
11
- @resize_dir || 'resized'
12
- end
13
-
14
- def self.resize(*args)
15
- assemble_path(*args)
16
- end
17
-
18
- def self.assemble_path(path, width, height, crop = false)
19
- File.join(assemble_dir(path), assemble_name(path, width, height, crop))
20
- end
21
-
22
- def self.assemble_dir(path)
23
- File.join(File.dirname(path), resize_dir)
24
- end
25
-
26
- def self.assemble_name(path, width, height, crop = false)
27
- File.basename(path, '.*') + assemble_command(width, height, crop) + File.extname(path)
28
- end
1
+ require 'fileutils'
2
+ require 'rake'
3
+ require 'mini_magick'
29
4
 
30
- def self.assemble_command(width, height, crop = false)
31
- width, height = width.to_i, height.to_i
32
- prefix = "-#{width == 0 ? nil : width}x#{height == 0 ? nil : height}"
33
- prefix + (crop ? 'crop' : '')
5
+ module PhotoCook
6
+ def self.rails_env?
7
+ defined?(Rails)
34
8
  end
35
9
  end
36
10
 
37
- require 'photo-cook/engine' if defined?(Rails)
11
+ require 'photo-cook/dimensions'
12
+ require 'photo-cook/pixel-ratio'
13
+ require 'photo-cook/paths'
14
+ require 'photo-cook/command'
15
+ require 'photo-cook/assemble'
16
+ require 'photo-cook/callbacks'
17
+ require 'photo-cook/logging'
38
18
  require 'photo-cook/resizer'
39
19
  require 'photo-cook/middleware'
40
- require 'photo-cook/carrierwave'
41
20
  require 'photo-cook/magick-photo'
21
+ require 'photo-cook/carrierwave'
22
+ require 'photo-cook/resizing'
23
+
24
+ if PhotoCook.rails_env?
25
+ require 'photo-cook/engine'
26
+ require 'photo-cook/pixel-ratio-spy'
27
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: photo-cook
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.2
4
+ version: 1.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Yaroslav Konoplov
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2015-10-02 00:00:00.000000000 Z
11
+ date: 2015-12-26 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rack
@@ -49,15 +49,25 @@ files:
49
49
  - Gemfile
50
50
  - Gemfile.lock
51
51
  - Rakefile
52
+ - app/assets/javascripts/photo-cook.js
53
+ - app/assets/javascripts/photo-cook/photo-cook.js.erb
52
54
  - lib/photo-cook.rb
55
+ - lib/photo-cook/assemble.rb
56
+ - lib/photo-cook/callbacks.rb
53
57
  - lib/photo-cook/carrierwave.rb
58
+ - lib/photo-cook/command.rb
59
+ - lib/photo-cook/dimensions.rb
54
60
  - lib/photo-cook/engine.rb
61
+ - lib/photo-cook/logging.rb
55
62
  - lib/photo-cook/magick-photo.rb
56
63
  - lib/photo-cook/middleware.rb
64
+ - lib/photo-cook/paths.rb
65
+ - lib/photo-cook/pixel-ratio-spy.rb
66
+ - lib/photo-cook/pixel-ratio.rb
57
67
  - lib/photo-cook/resizer.rb
68
+ - lib/photo-cook/resizing.rb
58
69
  - lib/photo-cook/version.rb
59
70
  - photo-cook.gemspec
60
- - vendor/assets/javascripts/photo-cook/photo-cook.js.erb
61
71
  homepage: http://github.com/yivo/photo-cook
62
72
  licenses:
63
73
  - MIT
@@ -1,49 +0,0 @@
1
- window.PhotoCook = {
2
- resizeDir: function() {
3
- return <%= PhotoCook.resize_dir.to_json %>;
4
- },
5
-
6
- pixelRatio: (function() {
7
- var mediaQuery = '(-webkit-min-device-pixel-ratio: 1.5), (min--moz-device-pixel-ratio: 1.5), (-o-min-device-pixel-ratio: 3/2), (min-resolution: 1.5dppx)';
8
- return function() {
9
- var ratio = window.devicePixelRatio;
10
-
11
- // If no ratio found check if screen is retina
12
- // and if so return 2x ratio
13
- if (ratio == null && typeof window.matchMedia === 'function') {
14
- if (window.matchMedia(mediaQuery).matches) {
15
- ratio = 2;
16
- }
17
- }
18
-
19
- return ratio != null ? ratio : 1;
20
- };
21
- })()
22
- };
23
-
24
- window.PhotoCook.resize = function(path, width, height, crop) {
25
- if (path == null) { return path; }
26
-
27
- var ratio = PhotoCook.pixelRatio();
28
- var command = '-' + (width ? Math.round(width * ratio) : '')
29
- + 'x'
30
- + (height ? Math.round(height * ratio) : '')
31
- + (crop ? 'crop' : '');
32
-
33
- var directory = path.slice(0, Math.max(path.lastIndexOf('/'), 0));
34
-
35
- var index = path.lastIndexOf('.');
36
- var extension = index >= 0 ? path.slice(index) : '';
37
-
38
- var basename = path.slice(
39
- directory ? directory.length + 1 : 0,
40
- extension ? 0 - extension.length : path.length
41
- );
42
-
43
- return (directory ? directory + '/' : directory)
44
- + PhotoCook.resizeDir()
45
- + '/'
46
- + basename
47
- + command
48
- + extension;
49
- };