photo-cook 1.0.2 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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
- };