photo-cook 1.0.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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 6607cf1acfc43a8d2f3ec8877856f3f0e947726d
4
+ data.tar.gz: 21d67faa51dd1040a7c22250af93d20b5e61d51d
5
+ SHA512:
6
+ metadata.gz: 8d3fcf83ba40a11f12940f229e9ce7d5b0b94ae5530360bf23e0453cdff7a31bc9f1e465a7ce24181b9d9858d93af28b51fbcbe1f4bbc457728ccacd960183d1
7
+ data.tar.gz: 1f25bb170da95d5bf5275ea7aa6eb42ead54c531cba7527dc388ed65349c7267a946f5fd1d58d46543d4842580e99d2ac3a106790ea0f875d9507716b928b79d
data/.gitignore ADDED
@@ -0,0 +1,4 @@
1
+ .bundle/
2
+ log/*.log
3
+ pkg/
4
+ .idea
data/Gemfile ADDED
@@ -0,0 +1,16 @@
1
+ source "https://rubygems.org"
2
+
3
+ # Declare your gem's dependencies in photo_cook.gemspec.
4
+ # Bundler will treat runtime dependencies like base dependencies, and
5
+ # development dependencies will be added by default to the :development group.
6
+ gemspec
7
+
8
+ # Declare any dependencies that are still in development here instead of in
9
+ # your gemspec. These might include edge Rails or gems from your path or
10
+ # Git. Remember to move these dependencies to your gemspec before releasing
11
+ # your gem to rubygems.org.
12
+
13
+ # To use debugger
14
+ # gem 'debugger'
15
+
16
+ gem 'mini_magick'
data/Gemfile.lock ADDED
@@ -0,0 +1,19 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ photo-cook (1.0.0)
5
+ mini_magick (~> 4.0)
6
+ rack (~> 1.5)
7
+
8
+ GEM
9
+ remote: https://rubygems.org/
10
+ specs:
11
+ mini_magick (4.1.0)
12
+ rack (1.5.5)
13
+
14
+ PLATFORMS
15
+ ruby
16
+
17
+ DEPENDENCIES
18
+ mini_magick
19
+ photo-cook!
data/Rakefile ADDED
@@ -0,0 +1,32 @@
1
+ begin
2
+ require 'bundler/setup'
3
+ rescue LoadError
4
+ puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
5
+ end
6
+
7
+ require 'rdoc/task'
8
+
9
+ RDoc::Task.new(:rdoc) do |rdoc|
10
+ rdoc.rdoc_dir = 'rdoc'
11
+ rdoc.title = 'PhotoCook'
12
+ rdoc.options << '--line-numbers'
13
+ rdoc.rdoc_files.include('README.rdoc')
14
+ rdoc.rdoc_files.include('lib/**/*.rb')
15
+ end
16
+
17
+
18
+
19
+
20
+ Bundler::GemHelper.install_tasks
21
+
22
+ require 'rake/testtask'
23
+
24
+ Rake::TestTask.new(:test) do |t|
25
+ t.libs << 'lib'
26
+ t.libs << 'test'
27
+ t.pattern = 'test/**/*_test.rb'
28
+ t.verbose = false
29
+ end
30
+
31
+
32
+ task default: :test
@@ -0,0 +1,9 @@
1
+ module PhotoCook
2
+ module CarrierWave
3
+
4
+ def resize(*args)
5
+ PhotoCook.resize(url, *args)
6
+ end
7
+
8
+ end
9
+ end
@@ -0,0 +1,9 @@
1
+ module PhotoCook
2
+ class Engine < ::Rails::Engine
3
+
4
+ initializer :javascripts do |app|
5
+ app.config.assets.paths << File.join(PhotoCook::Engine.root, 'app/assets/javascripts')
6
+ end
7
+
8
+ end
9
+ end
@@ -0,0 +1,6 @@
1
+ module PhotoCook
2
+ class MagickPhoto < MiniMagick::Image
3
+ attr_accessor :source_path
4
+ attr_accessor :resized_path
5
+ end
6
+ end
@@ -0,0 +1,107 @@
1
+ # To use this middleware you should configure application:
2
+ # application.config.middleware.insert_before(Rack::Sendfile, PhotoCook::Middleware, Rails.root)
3
+ require 'rack'
4
+
5
+ module PhotoCook
6
+ class Middleware
7
+
8
+ def initialize(app, root)
9
+ @app, @root = app, root
10
+ end
11
+
12
+ # The example will be concentrated around uri:
13
+ # /uploads/photos/resized/car-640x320crop.png
14
+ def call(env)
15
+ uri = extract_uri(env)
16
+
17
+ return default_actions(env) unless
18
+
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) &&
24
+
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, '')
31
+
32
+ return default_actions(env) if requested_file_exists?(uri)
33
+
34
+ # Regex match: _640x320crop
35
+ command = Regexp.last_match
36
+
37
+ # At this point we are sure that this request is targeting to resize photo
38
+
39
+ # Lets assemble path of the source photo
40
+ source_path = assemble_source_path(dir, photo_basename + File.extname(photo_name))
41
+
42
+ # Finally resize photo
43
+ resizer = PhotoCook::Resizer.instance
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]
47
+
48
+ if photo
49
+ log(photo, source_path, uri, command) if defined?(Rails)
50
+
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
56
+ else
57
+ default_actions(env)
58
+ end
59
+ end
60
+
61
+ private
62
+
63
+ def assemble_source_path(dir, photo_name)
64
+ URI.decode File.join(@root, PhotoCook.public_dir, dir, photo_name)
65
+ end
66
+
67
+ def requested_file_exists?(uri)
68
+ # /my_awesome_project_root/public/uploads/photos/resized/car-640x320crop.png
69
+ File.exists? File.join(@root, PhotoCook.public_dir, uri)
70
+ end
71
+
72
+ def extract_uri(env)
73
+ Rack::Utils.unescape(env['PATH_INFO'])
74
+ end
75
+
76
+ def default_actions(env)
77
+ @app.call(env)
78
+ end
79
+
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
+ }
105
+ end
106
+ end
107
+ end
@@ -0,0 +1,91 @@
1
+ # Resize algorithms from
2
+ # https://github.com/carrierwaveuploader/carrierwave/blob/71cb18bba4a2078524d1ea683f267d3a97aa9bc8/lib/carrierwave/processing/rmagick.rb
3
+
4
+ module PhotoCook
5
+ class Resizer
6
+ include Singleton
7
+
8
+ CENTER_GRAVITY = 'Center'
9
+ TRANSPARENT_BACKGROUND = 'rgba(255,255,255,0.0)'
10
+ PHOTO_QUALITY = 100
11
+
12
+ def resize(photo_path, width, height, crop = false)
13
+ if crop
14
+ resize_to_fill photo_path, width, height
15
+ else
16
+ resize_to_fit photo_path, width, height
17
+ end
18
+ end
19
+
20
+ # Resize the photo to fit within the specified dimensions:
21
+ # - the original aspect ratio will be kept
22
+ # - new dimensions will be not larger then the specified
23
+ def resize_to_fit(photo_path, width, height)
24
+ # Do nothing if photo is not valid so exceptions will be not thrown
25
+ return unless (photo = open(photo_path)).try(:valid?)
26
+
27
+ photo.combine_options do |cmd|
28
+ cmd.quality PHOTO_QUALITY
29
+ cmd.resize "#{width == 0 ? nil : width}x#{height == 0 ? nil : height}"
30
+ end
31
+
32
+ store photo, PhotoCook.assemble_path(photo_path, width, height, false)
33
+ end
34
+
35
+ # Resize the photo to fit within the specified dimensions:
36
+ # - new dimensions will be the same as specified
37
+ # - the photo will be cropped if necessary
38
+ def resize_to_fill(photo_path, width, height)
39
+ # Do nothing if photo is not valid so exceptions will be not thrown
40
+ return unless (photo = open(photo_path)).try(:valid?)
41
+
42
+ cols, rows = photo[:dimensions]
43
+ photo.combine_options do |cmd|
44
+ if width != cols || height != rows
45
+ scale_x = width / cols.to_f
46
+ scale_y = height / rows.to_f
47
+ if scale_x >= scale_y
48
+ cols = (scale_x * (cols + 0.5)).round
49
+ rows = (scale_x * (rows + 0.5)).round
50
+ cmd.resize "#{cols}"
51
+ else
52
+ cols = (scale_y * (cols + 0.5)).round
53
+ rows = (scale_y * (rows + 0.5)).round
54
+ cmd.resize "x#{rows}"
55
+ end
56
+ end
57
+ cmd.gravity CENTER_GRAVITY
58
+ cmd.background TRANSPARENT_BACKGROUND
59
+ cmd.quality PHOTO_QUALITY
60
+ cmd.extent "#{width}x#{height}" if cols != width || rows != height
61
+ end
62
+
63
+ store photo, PhotoCook.assemble_path(photo_path, width, height, true)
64
+ end
65
+
66
+ private
67
+
68
+ def open(photo_path)
69
+ begin
70
+ # MiniMagick::Image.open creates a temporary file for us and protects original
71
+ photo = MagickPhoto.open(photo_path)
72
+ photo.source_path = photo_path
73
+ photo
74
+ rescue
75
+ nil
76
+ end
77
+ end
78
+
79
+ def store(resized_photo, path_to_store_at)
80
+ dir = File.dirname path_to_store_at
81
+ Dir.mkdir dir unless File.exists?(dir)
82
+ resized_photo.write path_to_store_at
83
+ resized_photo.resized_path = path_to_store_at
84
+ resized_photo
85
+ end
86
+
87
+ def normalize_dimensions(w, h)
88
+ [w.to_i == 0 ? nil : w.to_i, h.to_i == 0 ? nil : h.to_i]
89
+ end
90
+ end
91
+ end
@@ -0,0 +1,3 @@
1
+ module PhotoCook
2
+ VERSION = '1.0.0'
3
+ end
data/lib/photo-cook.rb ADDED
@@ -0,0 +1,41 @@
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 PhotoCook.assemble_dir(path), PhotoCook.assemble_name(path, width, height, crop)
20
+ end
21
+
22
+ def self.assemble_dir(path)
23
+ File.join File.dirname(path), PhotoCook.resize_dir
24
+ end
25
+
26
+ def self.assemble_name(path, width, height, crop = false)
27
+ File.basename(path, '.*') + PhotoCook.assemble_command(width, height, crop) + File.extname(path)
28
+ end
29
+
30
+ def self.assemble_command(width, height, crop = false)
31
+ prefix = "-#{width == 0 ? '' : width}x#{height == 0 ? '' : height}"
32
+ prefix + (crop ? 'crop' : '')
33
+ end
34
+
35
+ end
36
+
37
+ require 'photo-cook/engine' if defined?(Rails)
38
+ require 'photo-cook/resizer'
39
+ require 'photo-cook/middleware'
40
+ require 'photo-cook/carrierwave'
41
+ require 'photo-cook/magick-photo'
@@ -0,0 +1,16 @@
1
+ require File.expand_path('../lib/photo-cook/version', __FILE__)
2
+
3
+ Gem::Specification.new do |s|
4
+ s.name = 'photo-cook'
5
+ s.version = PhotoCook::VERSION
6
+ s.authors = ['Yaroslav Konoplov']
7
+ s.email = ['yaroslav@inbox.com']
8
+ s.homepage = 'http://github.com/yivo/photo-cook'
9
+ s.summary = 'Simple solution for photo resizing'
10
+ s.description = 'This is a simple solution for photo resizing.'
11
+ s.license = 'MIT'
12
+ s.files = `git ls-files`.split("\n")
13
+
14
+ s.add_dependency 'rack', '~> 1.5'
15
+ s.add_dependency 'mini_magick', '~> 4.0'
16
+ end
@@ -0,0 +1,49 @@
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
+ };
metadata ADDED
@@ -0,0 +1,85 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: photo-cook
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Yaroslav Konoplov
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2015-09-04 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rack
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.5'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.5'
27
+ - !ruby/object:Gem::Dependency
28
+ name: mini_magick
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '4.0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '4.0'
41
+ description: This is a simple solution for photo resizing.
42
+ email:
43
+ - yaroslav@inbox.com
44
+ executables: []
45
+ extensions: []
46
+ extra_rdoc_files: []
47
+ files:
48
+ - ".gitignore"
49
+ - Gemfile
50
+ - Gemfile.lock
51
+ - Rakefile
52
+ - lib/photo-cook.rb
53
+ - lib/photo-cook/carrierwave.rb
54
+ - lib/photo-cook/engine.rb
55
+ - lib/photo-cook/magick-photo.rb
56
+ - lib/photo-cook/middleware.rb
57
+ - lib/photo-cook/resizer.rb
58
+ - lib/photo-cook/version.rb
59
+ - photo-cook.gemspec
60
+ - vendor/assets/javascripts/photo-cook/photo-cook.js.erb
61
+ homepage: http://github.com/yivo/photo-cook
62
+ licenses:
63
+ - MIT
64
+ metadata: {}
65
+ post_install_message:
66
+ rdoc_options: []
67
+ require_paths:
68
+ - lib
69
+ required_ruby_version: !ruby/object:Gem::Requirement
70
+ requirements:
71
+ - - ">="
72
+ - !ruby/object:Gem::Version
73
+ version: '0'
74
+ required_rubygems_version: !ruby/object:Gem::Requirement
75
+ requirements:
76
+ - - ">="
77
+ - !ruby/object:Gem::Version
78
+ version: '0'
79
+ requirements: []
80
+ rubyforge_project:
81
+ rubygems_version: 2.4.8
82
+ signing_key:
83
+ specification_version: 4
84
+ summary: Simple solution for photo resizing
85
+ test_files: []